具有多态对象的数组的JSON序列化

问题描述:

使用.NET标准的JavascriptSerializer/JsonDataContractSerializer或外部解析器可以使用包装器方法(包括对象类型)序列化对象数组吗?具有多态对象的数组的JSON序列化

例如,从列表生成此JSON:

[{ 'dog': { ...dog properties... } }, 
{ 'cat': { ...cat properties... } }] 

代替典型:

[{ ...dog properties... }, 
{ ...cat properties... }] 

这与使用JsonTypeInfo.As.WRAPPER_OBJECT属性杰克逊在Java中是可行的。

+0

嘿,你找到一个解决方案这我目前面临着类似的问题?服务器(Java,带有Jersey的Glassfish)将对象序列化为JSON,客户端(C#)需要反序列化这个对象。当使用XML一切工作正常... – hage 2012-03-09 13:24:04

也许我看到的最接近的是使用JavaScriptSerializer并将JavaScriptTypeResolver传递给构造函数。它不会生成与您的问题完全相同的JSON格式,但它确实有一个_type字段,它描述了要序列化的对象的类型。它可能会变得有点难看,但也许它会为你制造诡计。

这里是我的示例代码:

public abstract class ProductBase 
{ 
    public String Name { get; set; } 
    public String Color { get; set; } 
} 

public class Drink : ProductBase 
{ 
} 

public class Product : ProductBase 
{ 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<ProductBase> products = new List<ProductBase>() 
     { 
      new Product() { Name="blah", Color="Red"}, 
      new Product(){ Name="hoo", Color="Blue"}, 
      new Product(){Name="rah", Color="Green"}, 
      new Drink() {Name="Pepsi", Color="Brown"} 
     }; 

     JavaScriptSerializer ser = new JavaScriptSerializer(new SimpleTypeResolver()); 

     Console.WriteLine(ser.Serialize(products));  
    } 
} 

而结果是这样的:

[ 
    {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neutral, Publ 
icKeyToken=null","Name":"blah","Color":"Red"}, 
    {"__type":"TestJSON1.Product, Test 
JSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"hoo","Colo 
r":"Blue"}, 
    {"__type":"TestJSON1.Product, TestJSON1, Version=1.0.0.0, Culture=neu 
tral, PublicKeyToken=null","Name":"rah","Color":"Green"}, 
    {"__type":"TestJSON1.Dr 
ink, TestJSON1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Name":"P 
epsi","Color":"Brown"} 
] 

我使用SimpleTypeConverter,默认情况下为框架的一部分。您可以自行创建,以缩短__type返回的内容。

编辑:如果我创造我自己的JavaScriptTypeResolver缩短类型名称回来后,我会产生这样的:

[ 
    {"__type":"TestJSON1.Product","Name":"blah","Color":"Red"}, 
    {"__type":"TestJSON1.Product","Name":"hoo","Color":"Blue"}, 
    {"__type":"TestJSON1.Product","Name":"rah","Color":"Green"}, 
    {"__type":"TestJSON1.Drink","Name":"Pepsi","Color":"Brown"} 
] 

使用该转换器类:

public class MyTypeResolver : JavaScriptTypeResolver 
{ 
    public override Type ResolveType(string id) 
    { 
     return Type.GetType(id); 
    } 

    public override string ResolveTypeId(Type type) 
    { 
     if (type == null) 
     { 
      throw new ArgumentNullException("type"); 
     } 

     return type.FullName; 
    } 
} 

而只是路过它进入我的JavaScriptSerializer构造函数(而不是SimpleTypeConverter)。

我希望这有助于!

+0

谢谢你的详细答案(我会upvote)。我意识到这个解决方案,但希望与在Java中实现的客户端互操作,但无法接收这些“_​​_type”属性。 – ggarber 2011-03-04 08:48:04

+0

非常感谢你,这正是我一直在寻找的! – 2013-02-15 14:25:48

+0

@PeterMorris - 不客气。我最终也写了一篇关于它的博客文章。这是一个有趣的问题要解决。 http://geekswithblogs.net/DavidHoerster/archive/2012/01/06/json.net-and-deserializing-anonymous-types.aspx – 2013-02-15 14:50:29

Json.NET对此有一个很好的解决方案。还有就是增加了智能型信息的设置 - 声明它是这样的:

new JsonSerializer { TypeNameHandling = TypeNameHandling.Auto }; 

这将决定是否需要类型嵌入并添加必要。假设我有以下类:

public class Message 
{ 
    public object Body { get; set; } 
} 

public class Person 
{ 
    public string Name { get; set; } 
} 

public class Manager : Person 
{ 

} 

public class Department 
{ 
    private List<Person> _employees = new List<Person>(); 
    public List<Person> Employees { get { return _employees; } } 
} 

注意消息体是对象类型的,而且管理器子类是Person。如果我用序列一处身一个消息,有一个经理,我得到这样的:

{ 
    "Body": 
    { 
     "$type":"Department, MyAssembly", 
     "Employees":[ 
      { 
       "$type":"Manager, MyAssembly", 
       "Name":"Tim" 
      }] 
    } 
} 

注意它是如何增加$ type属性描述部和经理的类型。如果我现在一个人添加到雇员名单,并更改消息体的类型为系这样的:

public class Message 
{ 
    public Department Body { get; set; } 
} 

则不再需要体型注释和新的人没有被标注 - 没有注释假定元素实例是已声明的数组类型。序列化格式变为:

{ 
    "Body": 
    { 
     "Employees":[ 
      { 
       "$type":"Manager, MyAssembly", 
       "Name":"Tim" 
      }, 
      { 
       "Name":"James" 
      }] 
    } 
} 

这是一种高效方法 - 类型注释仅在必要时添加。虽然这是.NET特定的,但这种方法足够简单,可以轻松地处理其他平台上的反序列化器/消息类型,以便处理此问题。

虽然我在公开的API中使用这个,但是它是非标准的,因为它是非标准的。在这种情况下,您希望避免多态,并在消息中使版本和类型信息具有非常明确的属性。

+2

可以创建一个SerializationBinder派生类,它可以为类型映射更友好的字符串。这然后绑定到SerializerSettings.Binder属性。这还没有我想要的那么干净。如果有一个属性可以添加到类中以指定类型代码应该是什么,那将是非常好的。 – 2013-12-11 15:21:20

1)您可以使用字典<字符串对象>做的工作,...

[{ “猫”:{ “名称”: “粉红”}},{“猫“:{” 名称 “:” 闪闪 “}},{” 狗 “:{” 名称 “:” 最大“}}]

public class Cat 
{ 
    public string Name { get; set; } 
} 

public class Dog 
{ 
    public string Name { get; set; } 
} 


    internal static void Main() 
    { 
     List<object> animals = new List<object>(); 
     animals.Add(new Cat() { Name = "Pinky" }); 
     animals.Add(new Cat() { Name = "Winky" }); 
     animals.Add(new Dog() { Name = "Max" }); 
     // Convert every item in the list into a dictionary 
     for (int i = 0; i < animals.Count; i++) 
     { 
      var animal = new Dictionary<string, object>(); 
      animal.Add(animals[i].GetType().Name, animals[i]); 
      animals[i] = animal; 
     } 
     var serializer = new JavaScriptSerializer(); 
     var json = serializer.Serialize(animals.ToArray()); 


     animals = (List<object>)serializer.Deserialize(json, animals.GetType()); 
     // convert every item in the dictionary back into a list<object> item 
     for (int i = 0; i < animals.Count; i++) 
     { 
      var animal = (Dictionary<string, object>)animals[i]; 
      animal = (Dictionary<string, object>)animal.Values.First(); 
      animals[i] = animal.Values.First(); 
     } 
    } 

2)或使用JavaScriptConverter也能够处理该序列化的类型。

[{ “猫”:{ “杂食”:真}},{ “土豚”:{ “食虫”:假}},{ “土豚”:{ “食虫”:真}}]

​​

我得到这个工作按的问题。不完全直截了当,但这里就是这样。在Json.NET中没有简单的方法来做到这一点。如果它支持可以插入自己的类型信息的预序列化回调,那将非常棒,但那是另一回事。

我有一个多态类实现的接口(IShape)。其中一个类是一个容器(复合模式),并包含一个包含对象的列表。我用接口做了这个,但是相同的概念适用于基类。

public class Container : IShape 
{ 
    public virtual List<IShape> contents {get;set;} 
    // implement interface methods 

按的问题,我想这是连载:

"container": { 
    "contents": [ 
     {"box": { "TopLeft": {"X": 0.0,"Y": 0.0},"BottomRight": {"X": 1.0, "Y": 1.0} } }, 
     {"line": {"Start": { "X": 0.0,"Y": 0.0},"End": {"X": 1.0,"Y": 1.0 }} }, 

要做到这一点,我写了一个包装类。每个实现接口的对象在包装器中都有一个属性。这将在序列化程序中设置属性名称。有条件的序列化确保使用正确的属性。所有接口方法都委派给包装类,并且访问者Accept()调用被定向到包装类。这意味着在使用接口的上下文中,Wrapped或unwrapped类将表现相同。

public class SerializationWrapper : IShape 
    { 
     [JsonIgnore] 
     public IShape Wrapped { get; set; } 
     // Accept method for the visitor - redirect visitor to the wrapped class 
     // so visitors will behave the same with wrapped or unwrapped. 
     public void Accept(IVisitor visitor) => Wrapped.Accept(visitor); 

     public bool ShouldSerializeline() => line != null; 
     // will serialize as line : { ... 
     public Line line { get =>Wrapped as Line;} 

     public bool ShouldSerializebox() => box != null; 
     public Box box { get => Wrapped as Box; } 

     public bool ShouldSerializecontainer() => container != null; 
     public Container container { get => Wrapped as Container; } 

     // IShape methods delegated to Wrapped 
     [JsonIgnore] 
     public Guid Id { get => Wrapped.Id; set => Wrapped.Id = value; } 

我也有一个访问者模式实现来遍历对象图。我已经得到了这个软件设计的其余部分,但是如果你只有一个简单的集合,你可以迭代集合并添加包装器。

public class SerializationVisitor : IVisitor 
    { 
     public void Visit(IContainer shape) 
     { 
      // replace list items with wrapped list items 
      var wrappedContents = new List<IShape>(); 
      shape.Contents.ForEach(s => { wrappedContents.Add(new SerializationWrapper(){ Wrapped = s}); s.Accept(this); }); 
      shape.Contents = wrappedContents; 
     } 

     public void Visit(ILine shape){} 
     public void Visit(IBox shape){} 
    } 

访问者用包装版本的类替换容器类的内容。

序列化,它会产生所需的输出。

 SerializationVisitor s = new SerializationVisitor(); 
     s.Visit(label); 

因为我已经有游客和我通​​过接口尽一切可能只是简单做我自己的序列,反正.......