将附加信息传递给 JsonConverter

Passing additional information to a JsonConverter

我发现自己经常这样做。我有一个看起来像这样的 class:

public class Foo
{
    public SomeEnum SomeValue { get; set; }
    public SomeAbstractBaseClass SomeObject { get; set; }
}

而我需要做的是根据 SomeValue 中的值反序列化从 SomeAbstractBaseClass 派生的 specfic class。所以我所做的是在整个 class 上放置一个 JsonConverterAttribute,然后编写一个派生自 JsonConverter 的自定义转换器,它将在其 ReadJson 中,首先检查 SomeValue然后有一些逻辑将 SomeObject 反序列化为特定的 class。这有效,但有点烦人。真正需要特殊处理的唯一部分是 SomeObject 属性,但我必须将转换器放在 class 的更高级别,并让我的转换器负责填充所有其他Foo 的成员(即 SomeValue,但您可以想象如果您有许多其他属性都适用于默认的反序列化行为)。如果在 JsonConverterReadJson 方法中只有某种方法可以访问父对象(或至少某些 属性 或它的属性),则可以避免这种情况。但似乎没有办法做到这一点。所以如果我可以做类似的事情:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var parent = //...somehow access the parent or at least SomeValue
    switch (parent.SomeValue)
    {
        case Value1:
        serialized.Deserialize<SpecificType1>(reader);   
        break;
        //... other cases
    }
}

有一个非常有暗示意义的参数existingValue,但它似乎总是为空?有更好的方法吗?

根据 JSON specification,JSON 对象是 "an unordered set of name/value pairs",因此在读取 SomeAbstractBaseClass 的实例时尝试访问父对象的 SomeValue 枚举不能保证有效 -- 因为它可能还没有被阅读。

所以,我首先想推荐几个备选设计。由于 Json.NET 基本上是一个合同序列化程序,如果多态对象本身传达其类型信息,而不是父容器对象,它将更容易使用。因此你可以:

  1. 沿着 .

  2. 将多态类型枚举移动到 SomeAbstractBaseClass
  3. 通过设置 JsonSerializerSettings.TypeNameHandling to TypeNameHandling.Auto.

  4. 使用 Json.NET 对多态类型的内置支持

话虽这么说,您可以通过在 JsonConverter, reading the JSON for your container class Foo into a JObject, splitting out the polymorphic properties for custom handling, and using JsonSerializer.Populate 中填充剩余的属性,稍微减轻您的痛苦 。您甚至可以通过创建一个为您执行此操作的抽象转换器来标准化此模式,使用自定义属性来确定要拆分的属性:

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonCustomReadAttribute : Attribute
{
}

public abstract class JsonCustomReadConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException("invalid type " + objectType.FullName);
        var value = existingValue ?? contract.DefaultCreator();
        var jObj = JObject.Load(reader);

        // Split out the properties requiring custom handling
        var extracted = contract.Properties
            .Where(p => p.AttributeProvider.GetAttributes(typeof(JsonCustomReadAttribute), true).Count > 0)
            .Select(p => jObj.ExtractProperty(p.PropertyName))
            .Where(t => t != null)
            .ToList();

        // Populare the properties not requiring custom handling.
        using (var subReader = jObj.CreateReader())
            serializer.Populate(subReader, value);

        ReadCustom(value, new JObject(extracted), serializer);

        return value;
    }

    protected abstract void ReadCustom(object value, JObject jObject, JsonSerializer serializer);

    public override bool CanWrite { get { return false; } }

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

public static class JsonExtensions
{
    public static JProperty ExtractProperty(this JObject obj, string name)
    {
        if (obj == null)
            throw new ArgumentNullException();
        var property = obj.Property(name);
        if (property == null)
            return null;
        property.Remove();
        return property;
    }
}

然后像这样使用它:

public abstract class SomeAbstractBaseClass
{
}

public class Class1 : SomeAbstractBaseClass
{
    public string Value1 { get; set; }
}

public class Class2 : SomeAbstractBaseClass
{
    public string Value2 { get; set; }
}

public static class SomeAbstractBaseClassSerializationHelper
{
    public static SomeEnum SerializedType(this SomeAbstractBaseClass baseObject)
    {
        if (baseObject == null)
            return SomeEnum.None;
        if (baseObject.GetType() == typeof(Class1))
            return SomeEnum.Class1;
        if (baseObject.GetType() == typeof(Class2))
            return SomeEnum.Class2;
        throw new InvalidDataException();
    }

    public static SomeAbstractBaseClass DeserializeMember(JObject jObject, string objectName, string enumName, JsonSerializer serializer)
    {
        var someObject = jObject[objectName];
        if (someObject == null || someObject.Type == JTokenType.Null)
            return null;
        var someValue = jObject[enumName];
        if (someValue == null || someValue.Type == JTokenType.Null)
            throw new JsonSerializationException("no type information");
        switch (someValue.ToObject<SomeEnum>(serializer))
        {
            case SomeEnum.Class1:
                return someObject.ToObject<Class1>(serializer);
            case SomeEnum.Class2:
                return someObject.ToObject<Class2>(serializer);
            default:
                throw new JsonSerializationException("unexpected type information");
        }
    }
}

public enum SomeEnum
{
    None,
    Class1,
    Class2,
}

[JsonConverter(typeof(FooConverter))]
public class Foo
{
    [JsonCustomRead]
    public SomeEnum SomeValue { get { return SomeObject.SerializedType(); } }

    [JsonCustomRead]
    public SomeAbstractBaseClass SomeObject { get; set; }

    public string SomethingElse { get; set; }
}

public class FooConverter : JsonCustomReadConverter
{
    protected override void ReadCustom(object value, JObject jObject, JsonSerializer serializer)
    {
        var foo = (Foo)value;
        foo.SomeObject = SomeAbstractBaseClassSerializationHelper.DeserializeMember(jObject, "SomeObject", "SomeValue", serializer);
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(Foo).IsAssignableFrom(objectType);
    }
}