使用json.NET创建自定义反序列化器只针对特定的字段

12

我正在尝试反序列化一些JSON:

{ 
   "a":1,
   "b":25,
   "c":"1-7",
   "obj1":{ 
      "a1":10,
      "b1":45,
      "c1":60
   },
   "obj2":[ 
      { 
         "a2":100,
         "b2":15,
         "c2":50
      },
      { 
         "e2":"1,2,5-7",
         "f2":"1,3-5",
         "a2":25
      }
   ]
}

我希望找到一种方法来定义仅适用于某些字段的自定义反序列化。

在下面的代码中,我将需要特殊处理(自定义处理)的字段与可能以某种方式自动完成的字段分开。

是否有可能自动反序列化“正常”的字段?(不需要任何特定的自定义处理)

[JsonConverter(typeof(ConfigurationSerializer))]
    public class Configuration
    {
        public int a { get; set; }
        public int b { get; set; }
        public Obj1 obj1 { get; set; }

        public int[] c { get; set; }
        public IList<Obj2> obj2 { get; set; }
    }

    public class ConfigurationSerializer : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jsonObject = JObject.Load(reader);

            Configuration configuration = new Configuration();

            // I would like this part to be automatic as I just repeat the default
            // In the real case, I have many fields here!
            configuration.a = (int)jsonObject["a"];
            configuration.b = (int)jsonObject["b"];
            configuration.obj1 = jsonObject["obj1"].ToObject<Obj1>();

            // I created the JsonConverter for those 2 properties
            configuration.c = myCustomProcessMethod(jsonObject["c"]);
            configuration.obj2 = myCustomProcessMethod2(jsonObject["obj2"].ToObject<ValletConfiguration>());

            return configuration;
        }

        public override bool CanConvert(Type objectType)
        {
            return typeof(Configuration).IsAssignableFrom(objectType);
        }
    }
2个回答

13

既然您已经在类型上使用了Json.NET属性进行注释,似乎更简单的解决方案是使用[JsonConverter(Type)][JsonProperty(ItemConverterType = Type)]将转换器放在相关属性上:

public class Configuration
{
    public int a { get; set; }
    public int b { get; set; }
    public Obj1 obj1 { get; set; }

    // Converts the entire list to a compressed string
    [JsonConverter(typeof(IntListConverter))]
    public int[] c { get; set; }

    // Converts each Obj2 item individually
    [JsonProperty(ItemConverterType = typeof(Obj2Converter))]
    public IList<Obj2> obj2 { get; set; }
}

然而,如果你需要在Configuration上保留转换器(或者实际上是将转换器添加到JsonSerializerSettings.Converters中并且无法向类型添加Json.NET属性),则可以使用JsonSerializer.Populate()填充标准属性,只要您首先从JObject中删除自定义属性即可:

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        var jsonObject = JObject.Load(reader);

        var configuration = (existingValue as Configuration ?? new Configuration());

        // I created the JsonConverter for those 2 properties
        configuration.c = myCustomProcessMethod(jsonObject["c"].RemoveFromLowestPossibleParent());
        configuration.obj2 = myCustomProcessMethod2(jsonObject["obj2"].RemoveFromLowestPossibleParent().ToObject<ValletConfiguration>());

        // Populate the remaining standard properties
        using (var subReader = jsonObject.CreateReader())
        {
            serializer.Populate(subReader, configuration);
        }

        return configuration;
    }

使用扩展方法:

public static class JsonExtensions
{
    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
        if (contained != null)
            contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (node.Parent is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
}

感谢指出 using (JsonReader subReader = jObject.CreateReader())。对我来说,这似乎是最重要的部分。 - Mikhail
1
在创建读取器的副本时要注意。有关问题和解决方案的更多详细信息请参见此处:https://dev59.com/bGsz5IYBdhLWcg3wQFUq#8031283。话虽如此,+1,这很有帮助。 - Cristian Diaconescu

3
一种可能的方法是创建一个代理属性以正确地序列化和反序列化。通过使用 ScriptIgnoreAttribute(也可以使用 JsonIgnoreAttribute),真实属性不会被序列化。以下是一个示例:
[ScriptIgnore]
public int RealProperty { get; set; }

public string RealPropertyProxy
{
    get
    {
        return SerializeRealProperty(RealProperty);
    }
    set
    {
        RealProperty = DeserializeRealProperty(value);
    }
}

结果就是只有代理按照您定义的方式序列化(基于真实属性的值)。只需修改需要以特殊方式序列化的属性,您就不需要实现特殊的JsonConverter


虽然这是一种完成任务的方法,但对我来说问题在于每次读取属性时都需要反序列化。对于复杂对象,这可能会导致性能瓶颈。第一个答案中的方法只执行一次反序列化。 - Andries Koorzen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接