将其他信息传递给JsonConverter [英] Passing additional information to a JsonConverter

查看:63
本文介绍了将其他信息传递给JsonConverter的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现自己经常这样做.我有一堂课,看起来像这样:

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

我需要做的是基于SomeValue中的值反序列化从SomeAbstractBaseClass派生的 specfic 类.因此,我要做的是在整个类上放置一个JsonConverterAttribute,然后编写一个从JsonConverter派生的自定义转换器,该转换器将在其ReadJson中,首先检查SomeValue,然后具有一些将SomeObject反序列化为a的逻辑具体类别.这可行,但是有点烦人.唯一真正需要特殊处理的部分是SomeObject属性,但是我必须将转换器置于类的更高级别,并让我的转换器负责填充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规范(一种JSON)对象是一组无序的名称/值对",因此不能保证在读取SomeAbstractBaseClass实例时尝试访问父级的SomeValue枚举-因为可能尚未读取. >

因此,我首先想提出几个替代设计.由于Json.NET本质上是协定序列化程序,因此如果多态对象本身传达其类型信息而不是父容器对象,则使用起来会更容易.因此,您可以:

  1. 将多态类型枚举沿 JsonSerializerSettings.TypeNameHandling TypeNameHandling.Auto .

话虽如此,您可以通过 ="nofollow noreferrer"> JsonConverter ,将容器类Foo的JSON读入JObject,将多态属性拆分以进行自定义处理,并使用

然后像这样使用它:

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

I find myself doing this a lot. I have a class that looks something like this:

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

And what I need to do is deserialize a specfic class derived from SomeAbstractBaseClass based on the value in SomeValue. So what I do is put a JsonConverterAttribute on the whole class and then write a custom converter derived from JsonConverter that will in its ReadJson, first examine SomeValue and then have some logic to deserialize SomeObject to a specific class. This works, but it's kind of annoying. The only part that really needs special handling is the SomeObject property, but I have to put the converter at the higher level of the class and have my converter be responsible for populating all the other members of Foo (i.e. SomeValue, but you could imagine if you had lots of other properties that were fine with the default deserialization behavior). This could be avoided if there was only some way to get access to the parent object (or at least some property or properties from it) in the ReadJson method of JsonConverter. But there doesn't seem to be any way to do that. So if I could do something like:

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

There is the very suggestively named existingValue parameter, but it always seems to be null? Is there a better way to do this?

According to the JSON specification, a JSON object is "an unordered set of name/value pairs", so trying to access the parent's SomeValue enum while reading an instance of SomeAbstractBaseClass isn't guaranteed to work -- as it might not have been read yet.

So, I'd first like to suggest a couple of alternative designs. Since Json.NET is basically a contract serializer, it will be easier to use if the polymorphic object itself conveys its type information, rather than parent container objects. Thus you could either:

  1. Move the polymorphic type enum into SomeAbstractBaseClass along the lines of Json.Net Serialization of Type with Polymorphic Child Object.

  2. Use Json.NET's built-in support for polymorphic types by setting JsonSerializerSettings.TypeNameHandling to TypeNameHandling.Auto.

That being said, you can reduce your pain somewhat by, inside a JsonConverter, reading the JSON for your container class Foo into a JObject, splitting out the polymorphic properties for custom handling, and using JsonSerializer.Populate to fill in the remaining properties. You can even standardize this pattern by creating an abstract converter that does this for you, using a custom attribute to determine which properties to split out:

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

And then use it like:

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

这篇关于将其他信息传递给JsonConverter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆