C# 自定义JsonConverter WriteJson不会更改子属性的序列化

C# 自定义JsonConverter WriteJson不会更改子属性的序列化,c#,json,serialization,json.net,converter,C#,Json,Serialization,Json.net,Converter,我一直觉得JSON序列化程序实际上遍历了整个对象的树,并在遇到的每个接口类型的对象上执行自定义JsonConverter的WriteJson函数——事实并非如此 我有以下类和接口: public interface IAnimal { string Name { get; set; } string Speak(); List<IAnimal> Children { get; set; } } public class Cat : IAnimal {

我一直觉得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";
    }

}
在这个例子中,是的,狗可以给孩子养猫,反之亦然。在转换器中,我想插入“type”属性,以便将其保存到序列化中。我有以下设置。(动物园只有一个名字和一个动物列表。为了简洁和懒惰,我没有把它放在这里;)

type属性显示在Ruff、Cleo和Rover上,但不显示在Fido和Fluffy上。我想WriteJson不是递归调用的。如何在那里获取该类型属性


顺便说一句,为什么它不像我期望的那样使用camel case IAnimals?

转换器没有应用到子对象的原因是因为
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);
}

Fiddle:

这里有一个想法,不是对每个属性进行反射,而是迭代通常序列化的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);
        }
    }
private void AddRemoveSerializedProperties(JObject JObject,基于MahMan的合同)
{
jObject.AddFirst(…);
foreach(jObject中记录的KeyValuePair属性)
{
if(propertyJToken.Value.Type!=JTokenType.Object)
继续;
JToken nestedJObject=propertyJToken.Value;
PropertyInfo clrProperty=baseContract.GetType().GetProperty(propertyJToken.Key);
MahMan nestedObjectValue=clrProperty.GetValue(baseContract)作为MahMan;
if(nestedObj!=null)
AddRemoveSerializedProperties((JObject)nestedJObject,nestedObjectValue);
}
}

对于父类型和子类型,我使用两个自定义转换器时遇到了这个问题。我发现的一个更简单的方法是,由于
JToken.FromObject()
的重载将
序列化程序作为参数,因此可以传递
WriteJson()中给出的序列化程序。但是,您需要从序列化程序中删除转换器,以避免对其进行递归调用(但在之后将其添加回):


但值得注意的是,这可能会导致忽略其他基于序列化的属性。例如,如果您将[JsonIgnore]放在属性上,它仍然会与上面的代码一起添加。这里有类似的问题吗?您能提供帮助吗?关于实现此功能的方法的任何更新都尊重这些属性吗?jObject来自何方?嗨,李,很抱歉,如果jToken类型为“Object”,那么jToken可以转换为jObject。我添加了缺少的行,如果您有任何其他问题,那么这是一种糟糕的做法:1。这不是安全威胁2。它将无法以与此父节点相同的方式序列化相同类型的子节点(因为缺少当前序列化程序)3。如果FormObject失败,则seralizer的状态已更改4。如果没有失败,应用转换器的顺序可能已经改变。
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);
}
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);
        }
    }
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
}