我总觉得JSON序列化程序实际遍历整个对象的树,并在它遇到的每个接口类型对象上执行自定义JsonConverter的WriteJson函数 - 不是这样。
我有以下类和接口:
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是
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);
}
}
}
在这个例子中,是的,狗可以为孩子养猫,反之亦然。在转换器中,我想插入&#34;类型&#34;属性,以便将其保存到序列化。我有以下设置。 (动物园只有一个名字和一份IAnimals。为了简洁和懒惰,我没有把它包括在内;)
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": []
}
]
}
]
}
类型属性显示在Ruff,Cleo和Rover上,但不适用于Fido和Fluffy。我猜WriteJson不是递归调用的。我如何在那里获得那种类型的财产?
顺便说一句,为什么它不是像我期望的那样的驼峰式IAnimals?
答案 0 :(得分:16)
您的转换器未应用于子对象的原因是因为JToken.FromObject()
在内部使用了序列化程序的新实例,而不了解您的转换器。有一个允许你传入序列化程序的重载,但如果你这样做,你会遇到另一个问题:因为你在转换器内并且你使用JToken.FromObject()
来尝试序列化父对象,你将会进入无限递归循环。 (JToken.FromObject()
调用序列化程序,它调用转换器,调用JToken.FromObject()
等。)
要解决此问题,您必须手动处理父对象。你可以毫不费力地使用一些反射来枚举父属性:
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);
}
答案 1 :(得分:1)
这是一个想法,而不是对每个属性进行反射,迭代通常序列化的JObject,然后更改您感兴趣的属性的标记。
这样你仍然可以利用所有''JsonIgnore''属性和内置的其他有吸引力的功能。
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken jToken = JToken.FromObject(value);
if (jToken.Type == JTokenType.Object)
{
JObject jObject = (JObject)jToken;
...
AddRemoveSerializedProperties(jObject, val);
...
}
...
}
然后
private void AddRemoveSerializedProperties(JObject jObject, MahMan baseContract)
{
jObject.AddFirst(....);
foreach (KeyValuePair<string, JToken> propertyJToken in jObject)
{
if (propertyJToken.Value.Type != JTokenType.Object)
continue;
JToken nestedJObject = propertyJToken.Value;
PropertyInfo clrProperty = baseContract.GetType().GetProperty(propertyJToken.Key);
MahMan nestedObjectValue = clrProperty.GetValue(baseContract) as MahMan;
if(nestedObj != null)
AddRemoveSerializedProperties((JObject)nestedJObject, nestedObjectValue);
}
}
答案 2 :(得分:1)
我在使用两个自定义转换器作为父子类型时遇到了这个问题。我发现一个更简单的方法是,由于JToken.FromObject()
的重载将serializer
作为参数,因此您可以传递在WriteJson()
中给定的序列化器。但是,您需要从序列化器中删除转换器,以避免对它进行递归调用(但请在之后将其重新添加):
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Converters.Remove(this);
JToken jToken = JToken.FromObject(value, serializer);
serializer.Converters.Add(this);
// Perform any necessary conversions on the object returned
}
答案 3 :(得分:0)
这里有一个解决您的问题的hacky解决方案,它可以完成工作并且看起来很整洁。
public class MyJsonConverter : JsonConverter
{
public const string TypePropertyName = "type";
private bool _dormant = false;
/// <summary>
/// A hack is involved:
/// " JToken.FromObject(value, serializer); " creates amn infinite loop in normal circumstances
/// for that reason before calling it "_dormant = true;" is called.
/// the result is that this JsonConverter will reply false to exactly one "CanConvert()" call.
/// this gap will allow to generate a a basic version without any extra properties, and then add them on the call with " JToken.FromObject(value, serializer); ".
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_dormant = true;
JToken t = JToken.FromObject(value, serializer);
if (t.Type == JTokenType.Object && value is IContent)
{
JObject o = (JObject)t;
o.AddFirst(new JProperty(TypePropertyName, value.GetType().Name));
o.WriteTo(writer);
}
else
{
t.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
if (_dormant)
{
_dormant = false;
return false;
}
return true;
}
}