C# 将附加信息传递给JsonConverter

C# 将附加信息传递给JsonConverter,c#,json.net,deserialization,C#,Json.net,Deserialization,我发现自己经常这样做。我有一个类似这样的类: public class Foo { public SomeEnum SomeValue { get; set; } public SomeAbstractBaseClass SomeObject { get; set; } } 我需要做的是根据SomeValue中的值反序列化从SomeAbstractBaseClass派生的特定类。因此,我要做的是在整个类上放置一个JsonConverterAttribute,然后编写一个从Jso

我发现自己经常这样做。我有一个类似这样的类:

public class Foo
{
    public SomeEnum SomeValue { get; set; }
    public SomeAbstractBaseClass SomeObject { get; set; }
}
我需要做的是根据
SomeValue
中的值反序列化从
SomeAbstractBaseClass
派生的特定类。因此,我要做的是在整个类上放置一个
JsonConverterAttribute
,然后编写一个从
JsonConverter
派生的自定义转换器,它将在
ReadJson
中,首先检查
SomeValue
,然后使用一些逻辑将
SomeObject
反序列化到特定类。这很管用,但有点烦人。唯一真正需要特殊处理的部分是
SomeObject
属性,但我必须将转换器放在类的更高级别,让我的转换器负责填充
Foo
(例如,
SomeValue
,但您可以想象,如果您有许多其他属性可以使用默认的反序列化行为)。如果只有某种方式可以访问父对象(或至少从父对象中访问一些属性),则可以避免这种情况在
JsonConverter
ReadJson
方法中。但是似乎没有任何方法可以做到这一点。因此,如果我可以做如下操作:

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
    }
}
public override object ReadJson(JsonReader reader,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
var parent=/…以某种方式访问父级或至少访问某个值
开关(parent.SomeValue)
{
案例价值1:
序列化。反序列化(读取器);
打破
//……其他案件
}
}
有一个非常有启发性的命名为
existingValue
的参数,但它似乎总是空的?有更好的方法吗?

根据,JSON对象是“一组无序的名称/值对”,因此,在读取
SomeAbstractBaseClass
的实例时尝试访问父对象的
SomeValue
枚举并不一定有效,因为它可能尚未被读取

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

  • 沿着的行将多态类型枚举移动到
    SomeAbstractBaseClass

  • 通过设置为,使用Json.NET对多态类型的内置支持

  • 这就是说,您可以在a中通过将容器类
    Foo
    的JSON读入
    JObject
    中,拆分多态属性以进行自定义处理,并使用填充其余属性来减轻痛苦。您甚至可以通过创建一个抽象转换器来标准化此模式使用自定义属性来确定要拆分的属性,您可以执行以下操作:

    [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);
        }
    }
    
    公共抽象类SomeAbstractBaseClass
    {
    }
    公共类Class1:SomeAbstractBaseClass
    {
    公共字符串值1{get;set;}
    }
    公共类Class2:SomeAbstractBaseClass
    {
    公共字符串值2{get;set;}
    }
    公共静态类SomeAbstractBaseClasseSerializationHelper
    {
    公共静态SomeEnum SerializedType(此SomeAbstractBaseClass baseObject)
    {
    if(baseObject==null)
    返回SomeEnum.None;
    if(baseObject.GetType()==typeof(Class1))
    返回SomeEnum.Class1;
    if(baseObject.GetType()==typeof(Class2))
    返回SomeEnum.Class2;
    抛出新的InvalidDataException();
    }
    公共静态SomeAbstractBaseClass反序列化成员(JObject JObject、string objectName、string enumName、JsonSerializer序列化程序)
    {
    var someObject=jObject[objectName];
    if(someObject==null | | someObject.Type==JTokenType.null)
    返回null;
    var someValue=jObject[enumName];
    if(someValue==null | | someValue.Type==JTokenType.null)
    抛出新的JsonSerializationException(“无类型信息”);
    开关(someValue.ToObject(序列化程序))
    {
    案例SomeEnum.Class1:
    返回someObject.ToObject(序列化程序);
    案例SomeEnum.Class2:
    返回someObject.ToObject(序列化程序);
    违约:
    抛出新的JsonSerializationException(“意外类型信息”);
    }
    }
    }
    公共枚举SomeEnum
    {
    没有一个
    第一类,
    第2类,
    }
    [JsonConverter(类型(FooConverter))]
    公开课Foo
    {
    [JsonCustomRead]
    公共SomeEnum SomeValue{get{return SomeObject.SerializedType();}
    [JsonCustomRead]
    公共SomeAbstractBaseClass SomeObject{get;set;}
    公共字符串SomethingElse{get;set;}
    }
    公共类FooConverter:JsonCustomReadConverter
    {
    受保护的重写void ReadCustom(对象值、JObject JObject、JsonSerializer序列化程序)
    {
    var foo=(foo)值;
    SomeObject=someAbstractBaseClasseSerializationHelper.DeserializeMember(jObject,“SomeObject”,“SomeValue”,serializer);
    }
    公共覆盖布尔CanConvert(类型objectType)
    {
    返回typeof(Foo).IsAssignableFrom(objectType);
    }
    }
    
    我觉得
    SomeEnum SomeValue{get;set;}
    应该是
    SomeAbstractBaseClass
    的属性,而不是父类。如果是,您可以遵循中的模式。我还注意到,根据,因此,您不必假定在
    SomeObject
    之前会遇到
    SomeValue
    。因此,即使在读取
    SomeValue
    时分配了默认的
    SomeObject
    ,也没有100%可靠的方法来使用
    existingValue
    。@dbc:这实际上是一个很好的观点。我通常会这样做(在较高级别的
    Foo
    )是