将附加信息传递给JsonConverter

时间:2015-08-10 19:15:32

标签: c# json.net deserialization

我发现自己做了很多。我有一个看起来像这样的课程:

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

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

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参数,但它似乎总是为空?有更好的方法吗?

1 个答案:

答案 0 :(得分:1)

根据JSON specification,JSON对象是&#34;一组无序的名称/值对&#34;,因此在阅读时尝试访问父{q} SomeValue枚举SomeAbstractBaseClass的实例无法保证正常工作 - 因为它可能尚未被阅读。

所以,我首先想提出一些替代设计。由于Json.NET基本上是一个契约序列化器,如果多态对象本身传达其类型信息而不是父容器对象,它将更容易使用。因此你可以:

  1. 将多态类型枚举沿Json.Net Serialization of Type with Polymorphic Child Object的行移动到SomeAbstractBaseClass

  2. 通过将JsonSerializerSettings.TypeNameHandling设置为TypeNameHandling.Auto,使用Json.NET对多态类型的内置支持。

  3. 话虽如此,您可以在JsonConverter内,将容器类Foo的JSON读入JObject,从而减​​轻您的痛苦有点,拆分自定义处理的多态属性,并使用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);
        }
    }