使用.NET标准JavascriptSerializer / JsonDataContractSerializer或外部解析器是否可以使用包装器方法(包括对象类型)序列化对象数组?
例如,要从List生成此JSON:
[{ 'dog': { ...dog properties... } },
{ 'cat': { ...cat properties... } }]
而非典型:
[{ ...dog properties... },
{ ...cat properties... }]
使用JsonTypeInfo.As.WRAPPER_OBJECT属性,这在Java中是可行的。
答案 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);
由于我已经拥有了访问者并且我正在通过接口完成所有操作,因此无论如何都可以轻松地执行我自己的序列化程序.......