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



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

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

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不是递归调用的。如何在那里获得该类型的属性?

顺便说一句,为什么它不像我期望的那样是骆驼式的动物呢?

转换器没有应用于子对象的原因是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:https://dotnetfiddle.net/sVWsE4

这里有一个想法,不是对每个属性进行反射,而是遍历正常序列化的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);
        }
    }

我在为父类型和子类型使用两个自定义转换器时遇到了这个问题。我发现的一个更简单的方法是,由于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
}

这里有一个简单的问题解决方案,可以完成工作并看起来整洁。

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

相关内容

  • 没有找到相关文章

最新更新