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

时间:2011-03-03 21:45:18

标签: .net json serialization polymorphism

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

例如,要从List生成此JSON:

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

而非典型:

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

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

4 个答案:

答案 0 :(得分:21)

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

注意Message Body是object类型,而Manager是子类Person。如果我使用具有单个Manager的Department Body序列化消息,我会得到:

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

注意它是如何添加$ type属性来描述Department和Manager类型的。如果我现在将一个Person添加到Employees列表并将Message Body更改为Department类型,如下所示:

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

然后不再需要Body类型注释,并且不对新Person进行注释 - 缺少注释假定元素实例属于声明的数组类型。序列化格式变为:

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

这是一种有效的方法 - 只在需要时添加类型注释。虽然这是特定于.NET的,但该方法很简单,可以处理其他平台上的反序列化器/消息类型应该相当容易地扩展来处理它。

我会谨慎地在公共API中使用它,因为它是非标准的。在这种情况下,您需要避免多态,并在消息中使版本控制和类型信息非常明确。

答案 1 :(得分:11)

我见过的最接近的可能就是使用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)。

我希望这有帮助!

答案 2 :(得分:0)

1)你可以使用Dictionary&lt; string,object&gt;做这项工作,......

  

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

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

  

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

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[])));
    }
}

答案 3 :(得分: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()调用被定向到包装类。这意味着在使用该接口的上下文中,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){}
    }

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

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

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

由于我已经拥有了访问者并且我正在通过接口完成所有操作,因此无论如何都可以轻松地执行我自己的序列化程序.......