Skip to main content
 首页 » 编程设计

c#之WebAPI JSON 序列化不序列化复合对象的任何子对象

2024年02月06日27grandyang

所以我需要将一个复合序列化为 JSON(使用 JSON.NET),并希望来到这里解决这个问题会是一个快速的胜利。

我有一个非常基本的复合实现,我只是想用它来构建我的服务和数据结构,但 JSONSerializer 只序列化根节点。

代码:

namespace Data 
{    
   public abstract class Element 
   { 
       protected string _name; 
       public Element(string name) 
       { 
           _name = name; 
       } 
       public abstract void Add(Element element); 
 
 
       public string Name { get { return _name; } } 
   } 
 
   public class ConcreteElement : Element 
   { 
      public ConcreteElement(string name) : base(name) { } 
      public override void Add(Element element) 
      { 
         throw new InvalidOperationException("ConcreteElements may not contain Child nodes. Perhaps you intended to add this to a Composite"); 
      } 
   } 
 
    public class Composite: Element 
    { 
       public Composite(string name) : base(name) { Elements = new List<Element>(); } 
       private List<Element> Elements { get; set; } 
       public override void Add(Element element) 
       { 
           Elements.Add(element); 
       } 
    } 
} 

在我的 Controller 的 HttpGet 方法中,

Composite root = new Composite("Root"); 
Composite branch = new Composite("Branch"); 
branch.Add(new ConcreteElement("Leaf1")); 
branch.Add(new ConcreteElement("Leaf2")); 
root.Add(branch); 
return JsonConvert.SerializeObject(root); 

唯一被序列化的是

{"Name\":\"Root\"}" 

谁能看出这不是序列化子元素的原因? 我希望这是愚蠢的事情。

编辑1

我以前从未尝试过使用 WebAPI 将图形序列化为 JSON。我是否需要编写自定义 MediaTypeFormatter 来序列化它?

Edit2(添加所需的输出)

Leaf1 和 Leaf2 目前只是标记。一旦我可以将其序列化,它们本身就是复杂的对象。 所以,此刻……

{ 
  "Name" : "Root" 
  ,"Branch":   
           [ 
              {"Name":"Leaf1"} 
             ,{"Name":"Leaf2"} 
             ] 
           ] 
} 

最终

{ 
   "Name" : "Root" 
  ,"Branch1": 
          [ 
            {"Name":"Leaf1", "Foo":"Bar"} 
            {"Name":"Leaf2", "Foo":"Baz"}  
          ] 
 ,"Branch2": 
          [ 
            "Branch3":[ 
                        {"Name":"Leaf3", "Foo":"Quux"} 
                      ] 
          ] 
} 

请您参考如下方法:

子元素未被序列化,因为您的 Composite 中的元素列表是私有(private)的。 Json.Net 默认不会序列化私有(private)成员。如果您使用 [JsonProperty("Elements")] 标记列表,则子项将被序列化。

public class Composite: Element 
{ 
   ... 
   [JsonProperty("Elements")] 
   private List<Element> Elements { get; set; } 
   ... 
} 

如果您运行带有此更改的示例代码,您应该获得以下 JSON:

{ 
  "Elements": [ 
    { 
      "Elements": [ 
        { 
          "Name": "Leaf1" 
        }, 
        { 
          "Name": "Leaf2" 
        } 
      ], 
      "Name": "Branch" 
    } 
  ], 
  "Name": "Root" 
} 

编辑

好的,这是您的复合 Material 的示例转换器:

class CompositeConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
        return (objectType == typeof(Composite)); 
    } 
 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
        Composite composite = (Composite)value; 
 
        // Need to use reflection here because Elements is private 
        PropertyInfo prop = typeof(Composite).GetProperty("Elements", BindingFlags.NonPublic | BindingFlags.Instance); 
        List<Element> children = (List<Element>)prop.GetValue(composite); 
 
        JArray array = new JArray(); 
        foreach (Element e in children) 
        { 
            array.Add(JToken.FromObject(e, serializer)); 
        } 
 
        JObject obj = new JObject(); 
        obj.Add(composite.Name, array); 
        obj.WriteTo(writer); 
    } 
 
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
        throw new NotImplementedException(); 
    } 
} 

这是一个演示:

class Program 
{ 
    static void Main(string[] args) 
    { 
        Composite root = new Composite("Root"); 
        Composite branch1 = new Composite("Branch1"); 
        branch1.Add(new ConcreteElement("Leaf1", "Bar")); 
        branch1.Add(new ConcreteElement("Leaf2", "Baz")); 
        root.Add(branch1); 
        Composite branch2 = new Composite("Branch2"); 
        branch2.Add(new ConcreteElement("Leaf3", "Quux")); 
        Composite branch3 = new Composite("Branch3"); 
        branch3.Add(new ConcreteElement("Leaf4", "Fizz")); 
        branch2.Add(branch3); 
        root.Add(branch2); 
        string json = JsonConvert.SerializeObject(root, Formatting.Indented, new CompositeConverter()); 
        Console.WriteLine(json); 
    } 
} 
 
public abstract class Element 
{ 
    protected string _name; 
    public Element(string name) 
    { 
        _name = name; 
    } 
    public abstract void Add(Element element); 
    public string Name { get { return _name; } } 
} 
 
public class ConcreteElement : Element 
{ 
    public ConcreteElement(string name, string foo) : base(name) 
    { 
        Foo = foo; 
    } 
    public string Foo { get; set; } 
    public override void Add(Element element) 
    { 
        throw new InvalidOperationException("ConcreteElements may not contain Child nodes. Perhaps you intended to add this to a Composite"); 
    } 
} 
 
public class Composite : Element 
{ 
    public Composite(string name) : base(name) { Elements = new List<Element>(); } 
    private List<Element> Elements { get; set; } 
    public override void Add(Element element) 
    { 
        Elements.Add(element); 
    } 
} 

这是生成的 JSON 输出:

{ 
  "Root": [ 
    { 
      "Branch1": [ 
        { 
          "Foo": "Bar", 
          "Name": "Leaf1" 
        }, 
        { 
          "Foo": "Baz", 
          "Name": "Leaf2" 
        } 
      ] 
    }, 
    { 
      "Branch2": [ 
        { 
          "Foo": "Quux", 
          "Name": "Leaf3" 
        }, 
        { 
          "Branch3": [ 
            { 
              "Foo": "Fizz", 
              "Name": "Leaf4" 
            } 
          ] 
        } 
      ] 
    } 
  ] 
} 

我意识到这与您要求的 JSON 不完全相同,但它应该能让您朝着正确的方向前进。您在问题中指定的“所需”JSON 的一个问题是它不完全有效。命名属性只能在对象内部,不能直接在数组内部。在您的第二个示例中,您在“Branch2”的数组中直接有一个名为“Branch3”的属性。这行不通。因此,您需要将 Branch2 设为一个对象。但是如果你这样做,那么你的组合表示不一致:如果它只包含叶子,那么它是一个数组,否则它是一个对象。可以制作一个转换器来根据内容更改组合的表示(事实上我设法创建了这样一个野兽),但这使得 JSON 更难以使用,最后我不认为你会想要使用它。如果您好奇,我在下面包含了这个备用转换器及其输出。

class CompositeConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
        return (objectType == typeof(Composite)); 
    } 
 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
        Composite composite = (Composite)value; 
 
        // Need to use reflection here because Elements is private 
        PropertyInfo prop = typeof(Composite).GetProperty("Elements", BindingFlags.NonPublic | BindingFlags.Instance); 
        List<Element> children = (List<Element>)prop.GetValue(composite); 
 
        // if all children are leaves, output as an array 
        if (children.All(el => el.GetType() != typeof(Composite))) 
        { 
            JArray array = new JArray(); 
            foreach (Element e in children) 
            { 
                array.Add(JToken.FromObject(e, serializer)); 
            } 
            array.WriteTo(writer); 
        } 
        else  
        { 
            // otherwise use an object 
            JObject obj = new JObject(); 
            if (composite.Name == "Root") 
            { 
                obj.Add("Name", composite.Name); 
            } 
            foreach (Element e in children) 
            { 
                obj.Add(e.Name, JToken.FromObject(e, serializer)); 
            } 
            obj.WriteTo(writer); 
        } 
    } 
 
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
        throw new NotImplementedException(); 
    } 
} 

使用相同的数据输出:

{ 
  "Name": "Root", 
  "Branch1": [ 
    { 
      "Foo": "Bar", 
      "Name": "Leaf1" 
    }, 
    { 
      "Foo": "Baz", 
      "Name": "Leaf2" 
    } 
  ], 
  "Branch2": { 
    "Leaf3": { 
      "Foo": "Quux", 
      "Name": "Leaf3" 
    }, 
    "Branch3": [ 
      { 
        "Foo": "Fizz", 
        "Name": "Leaf4" 
      } 
    ] 
  } 
}