问题描述
我一直觉得 JSON 序列化程序实际上会遍历整个对象的树,并在遇到的每个接口类型对象上执行自定义 JsonConverter 的 WriteJson 函数 - 并非如此.
I always had the impression that the JSON serializer actually traverses your entire object's tree, and executes the custom JsonConverter's WriteJson function on each interface-typed object that it comes across - not so.
我有以下类和接口:
public interface IAnimal
{
string Name { get; set; }
string Speak();
List<IAnimal> Children { get; set; }
}
public class Cat : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Cat()
{
Children = new List<IAnimal>();
}
public Cat(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Meow";
}
}
public class Dog : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Dog()
{
Children = new List<IAnimal>();
}
public Dog(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Arf";
}
}
为了避免 JSON 中的 $type 属性,我写了一个自定义的 JsonConverter 类,它的 WriteJson 是
To avoid the $type property in the JSON, I've written a custom JsonConverter class, whose WriteJson is
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
IAnimal animal = value as IAnimal;
JObject o = (JObject)t;
if (animal != null)
{
if (animal is Dog)
{
o.AddFirst(new JProperty("type", "Dog"));
//o.Find
}
else if (animal is Cat)
{
o.AddFirst(new JProperty("type", "Cat"));
}
foreach(IAnimal childAnimal in animal.Children)
{
// ???
}
o.WriteTo(writer);
}
}
}
在这个例子中,是的,狗可以为孩子养猫,反之亦然.在转换器中,我想插入类型"属性,以便将其保存到序列化中.我有以下设置.(Zoo 只有一个名称和一个 IAnimals 列表.为了简洁和懒惰,我没有在此处包含它;))
In this example, yes, a dog can have cats for children and vice-versa. In the converter, I want to insert the "type" property so that it saves that to the serialization. I have the following setup. (Zoo has only a name and a list of IAnimals. I didn't include it here for brevity and laziness ;))
Zoo hardcodedZoo = new Zoo()
{ Name = "My Zoo",
Animals = new List<IAnimal> { new Dog("Ruff"), new Cat("Cleo"),
new Dog("Rover"){
Children = new List<IAnimal>{ new Dog("Fido"), new Dog("Fluffy")}
} }
};
JsonSerializerSettings settings = new JsonSerializerSettings(){
ContractResolver = new CamelCasePropertyNamesContractResolver() ,
Formatting = Formatting.Indented
};
settings.Converters.Add(new AnimalsConverter());
string serializedHardCodedZoo = JsonConvert.SerializeObject(hardcodedZoo, settings);
serializedHardCodedZoo
序列化后有如下输出:
{
"name": "My Zoo",
"animals": [
{
"type": "Dog",
"Name": "Ruff",
"Children": []
},
{
"type": "Cat",
"Name": "Cleo",
"Children": []
},
{
"type": "Dog",
"Name": "Rover",
"Children": [
{
"Name": "Fido",
"Children": []
},
{
"Name": "Fluffy",
"Children": []
}
]
}
]
}
type 属性显示在 Ruff、Cleo 和 Rover 上,但不显示在 Fido 和 Fluffy 上.我猜 WriteJson 不是递归调用的.我如何在那里获得该类型的属性?
The type property shows up on Ruff, Cleo, and Rover, but not for Fido and Fluffy. I guess the WriteJson isn't called recursively. How do I get that type property there?
顺便说一句,为什么 IAnimals 不像我期望的那样是驼峰式的?
As an aside, why does it not camel-case IAnimals like I expect it to?
推荐答案
您的转换器没有被应用到您的子对象的原因是因为 JToken.FromObject()
使用了内部的序列化器,它不知道您的转换器.有一个重载允许您传入序列化程序,但如果在这里这样做,您将遇到另一个问题:因为您在转换器内部并且您正在使用 JToken.FromObject()
来尝试序列化父对象,您将进入无限递归循环.(JToken.FromObject()
调用序列化程序,它调用你的转换器,它调用 JToken.FromObject()
等)
The reason that your converter is not getting applied to your child objects is because JToken.FromObject()
uses a new instance of the serializer internally, which does not know about your converter. There is an overload that allows you to pass in the serializer, but if you do so here you will have another problem: since you are inside a converter and you are using JToken.FromObject()
to try to serialize the parent object, you will get into an infinite recursive loop. (JToken.FromObject()
calls the serializer, which calls your converter, which calls JToken.FromObject()
, etc.)
要解决此问题,您必须手动处理父对象.您可以使用一些反射来枚举父属性,而不会遇到太多麻烦:
To get around this problem, you must handle the parent object manually. You can do this without much trouble using a bit of reflection to enumerate the parent properties:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
Type type = value.GetType();
jo.Add("type", type.Name);
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
object propVal = prop.GetValue(value, null);
if (propVal != null)
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
jo.WriteTo(writer);
}
小提琴:https://dotnetfiddle.net/sVWsE4
这篇关于自定义 JsonConverter WriteJson 不会改变子属性的序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!