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

20

使用.NET标准的JavascriptSerializer/JsonDataContractSerializer或外部解析器,是否可以使用包装器方法序列化对象数组,其中包括对象类型?

例如,从List生成此JSON:

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

与其典型的方式:

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

使用Jackson在Java中是可行的,可以使用JsonTypeInfo.As.WRAPPER_OBJECT属性。


嘿,你找到解决这个问题的方法了吗?我目前也遇到了类似的问题。一个服务器(Java,Glassfish和Jersey)将对象序列化为JSON格式,而客户端(C#)需要对其进行反序列化。当使用XML时一切正常... - hage
4个回答

23

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; } }
}

注意消息正文是对象类型,而Manager是Person的子类。如果我序列化带有单个Manager的Department正文的消息,我会得到这个:

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

注意现在它添加了$type属性来描述部门和经理的类型。如果我现在将一个Person添加到Employees列表中,并将消息正文更改为Department类型,就像这样:

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

那么Body类型注释就不再需要了,新的Person也不需要注释 - 没有注释会假定元素实例属于声明的数组类型。序列化格式变为:

{
    "Body":
    {
        "Employees":[
            {
                "$type":"Manager, MyAssembly",
                "Name":"Tim"
            },
            {
                "Name":"James"
            }]
    }
}
这是一种高效的方法 - 类型注释仅在必要时添加。虽然这是.NET特定的,但这种方法足够简单,可以轻松扩展到其他平台上的反序列化器/消息类型。
不过,我会对在公共API中使用此方法持有保留意见,因为它是非标准的。在这种情况下,您应该避免多态,并使版本控制和类型信息成为消息中非常明确的属性。

2
可以创建一个继承自SerializationBinder的类,该类可以将稍微友好一些的字符串映射到类型。然后将其绑定到SerializerSettings.Binder属性。但这仍然不够干净。如果能有一个属性可以添加到类中以指定类型代码就更好了。 - Martin Brown

12

我看过的最接近你要求的方式是使用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。你可以创建自己的TypeConverter来缩短__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)。

希望这可以帮助!


感谢您详细的回答(我会点赞)。我知道这个解决方案,但是我想与一个用Java实现的客户端进行交互,而该客户端无法接收那些“__type”属性。 - ggarber
@PeterMorris - 不用谢。我实际上还写了一篇关于这个问题的博客文章。解决这个问题很有趣。http://geekswithblogs.net/DavidHoerster/archive/2012/01/06/json.net-and-deserializing-anonymous-types.aspx - David Hoerster

0

我按照问题要求完成了这个任务。虽然不是很直接,但还是做到了。在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()调用被指向包装类。这意味着在使用接口的上下文中,包装或未包装的类将表现相同。

    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);

既然我已经拥有了访问者并且通过接口进行所有操作,那么自己编写序列化程序可能同样容易......


0

1) 你可以使用 Dictionary<string,object> 来完成这项工作,...

[{"Cat":{"Name":"Pinky"}},{"Cat":{"Name":"Winky"}},{"Dog":{"Name":"Max"}}]

(注意转义字符)

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 可以处理类型的序列化。

[{"cat":{"杂食动物":true}},{"aardvark":{"昆虫食肉动物":false}},{"aardvark":{"昆虫食肉动物":true}}]

abstract class AnimalBase { }

class Aardvark : AnimalBase
{
    public bool Insectivore { get; set; }
}

class Dog : AnimalBase
{
    public bool Omnivore { get; set; }
}

class AnimalsConverter : JavaScriptConverter
{
    private IDictionary<string, Type> map;

    public AnimalsConverter(IDictionary<string, Type> map) { this.map = map; }

    public override IEnumerable<Type> SupportedTypes
    {
        get { return new Type[]{typeof(AnimalBase)}; }
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        var result = new Dictionary<string, object>();
        var type = obj.GetType();
        var name = from x in this.map where x.Value == type select x.Key;
        if (name.Count<string>() == 0)
            return null;
        var value = new Dictionary<string, object>();
        foreach (var prop in type.GetProperties())
        {
            if(!prop.CanRead) continue;
            value.Add(prop.Name, prop.GetValue(obj, null));
        }
        result.Add(name.First<string>(), value);
        return result;
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        var keys = from x in this.map.Keys where dictionary.ContainsKey(x) select x;
        if (keys.Count<string>() <= 0) return null;
        var key = keys.First<string>();
        var poly = this.map[key];
        var animal = (AnimalBase)Activator.CreateInstance(poly);
        var values = (Dictionary<string, object>)dictionary[key];
        foreach (var prop in poly.GetProperties())
        {
            if(!prop.CanWrite) continue;
            var value = serializer.ConvertToType(values[prop.Name], prop.PropertyType);
            prop.SetValue(animal, value, null);
        }
        return animal;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var animals = new List<AnimalBase>();
        animals.Add(new Dog() { Omnivore = true });
        animals.Add(new Aardvark() { Insectivore = false });
        animals.Add(new Aardvark() { Insectivore = true });
        var convertMap = new Dictionary<string, Type>();
        convertMap.Add("cat", typeof(Dog));
        convertMap.Add("aardvark", typeof(Aardvark));
        var converter = new AnimalsConverter(convertMap);
        var serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new JavaScriptConverter[] {converter});
        var json = serializer.Serialize(animals.ToArray());
        animals.Clear();
        animals.AddRange((AnimalBase[])serializer.Deserialize(json, typeof(AnimalBase[])));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接