【问题标题】:Using custom JsonConverter and TypeNameHandling in Json.net在 Json.net 中使用自定义 JsonConverter 和 TypeNameHandling
【发布时间】:2015-04-22 22:22:22
【问题描述】:

我有一个具有接口类型属性的类,例如:

public class Foo
{
    public IBar Bar { get; set; }
}

我还有多个可以在运行时设置的IBar 接口的具体实现。其中一些具体类需要自定义 JsonConverter 来进行序列化和反序列化。

利用TypeNameHandling.Auto 选项,需要IBar 类的非转换器可以完美地序列化和反序列化。另一方面,自定义序列化的类没有$type 名称输出,虽然它们按预期序列化,但它们不能反序列化为它们的具体类型。

我尝试在自定义JsonConverter 中自己写出$type 名称元数据;但是,在反序列化时,转换器将被完全绕过。

是否有解决方法或适当的方法来处理这种情况?

【问题讨论】:

    标签: c# json.net


    【解决方案1】:

    我解决了类似的问题并找到了解决方案。它不是很优雅,我认为应该有更好的方法,但至少它有效。所以我的想法是每个实现IBar 的类型都有JsonConverterIBar 本身的一个转换器。

    让我们从模型开始:

    public interface IBar { }
    
    public class BarA : IBar  { }
    
    public class Foo
    {
        public IBar Bar { get; set; }
    }
    

    现在让我们为IBar 创建转换器。仅在反序列化 JSON 时使用。它将尝试读取$type 变量并调用转换器以实现类型:

    public class BarConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotSupportedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jObj = JObject.Load(reader);
            var type = jObj.Value<string>("$type");
    
            if (type == GetTypeString<BarA>())
            {
                return new BarAJsonConverter().ReadJson(reader, objectType, jObj, serializer);
            }
            // Other implementations if IBar
    
            throw new NotSupportedException();
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof (IBar);
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        private string GetTypeString<T>()
        {
            var typeOfT = typeof (T);
            return string.Format("{0}, {1}", typeOfT.FullName, typeOfT.Assembly.GetName().Name);
        }
    }
    

    这是BarA 类的转换器:

    public class BarAJsonConverter : BarBaseJsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // '$type' property will be added because used serializer has TypeNameHandling = TypeNameHandling.Objects
            GetSerializer().Serialize(writer, value);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var existingJObj = existingValue as JObject;
            if (existingJObj != null)
            {
                return existingJObj.ToObject<BarA>(GetSerializer());
            }
    
            throw new NotImplementedException();
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(BarA);
        }
    }
    

    您可能会注意到它继承自 BarBaseJsonConverter 类,而不是 JsonConverter。而且我们不在WriteJsonReadJson 方法中使用serializer 参数。在自定义转换器中使用serializer 参数存在问题。你可以阅读更多here。我们需要创建JsonSerializer 的新实例,而基类是一个很好的候选对象:

    public abstract class BarBaseJsonConverter : JsonConverter
    {
        public JsonSerializer GetSerializer()
        {
            var serializerSettings = JsonHelper.DefaultSerializerSettings;
            serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
    
            var converters = serializerSettings.Converters != null
                ? serializerSettings.Converters.ToList()
                : new List<JsonConverter>();
            var thisConverter = converters.FirstOrDefault(x => x.GetType() == GetType());
            if (thisConverter != null)
            {
                converters.Remove(thisConverter);
            }
            serializerSettings.Converters = converters;
    
            return JsonSerializer.Create(serializerSettings);
        }
    }
    

    JsonHelper 只是一个创建JsonSerializerSettings 的类:

    public static class JsonHelper
    {
        public static JsonSerializerSettings DefaultSerializerSettings
        {
            get
            {
                return new JsonSerializerSettings
                {
                    Converters = new JsonConverter[] { new BarConverter(), new BarAJsonConverter() }
                };
            }
        }
    }
    

    现在它可以工作了,您仍然可以使用自定义转换器进行序列化和反序列化:

    var obj = new Foo { Bar = new BarA() };
    var json = JsonConvert.SerializeObject(obj, JsonHelper.DefaultSerializerSettings);
    var dObj = JsonConvert.DeserializeObject<Foo>(json, JsonHelper.DefaultSerializerSettings);
    

    【讨论】:

    • 感谢您的精彩回答!我昨晚在做类似的事情,但你给了我需要的额外信息来让它工作。我采用了一种稍微不同的方法,但它是完全通用的,不需要所有继承类型的转换器。请参阅下面的我的自我回答。不过,我会将其标记为答案,因为它让我走上了正确的道路。
    • 如果要转换的类型还引用了其他需要转换器的类型,则此方法将不起作用。您实际上是在禁用转换器的情况下反序列化整个子树。
    【解决方案2】:

    使用上面 Alesandr Ivanov 的回答中的信息,我创建了一个通用的 WrappedJsonConverter&lt;T&gt; 类,该类包装(和解包)需要使用 $wrappedType 元数据属性的转换器的具体类,该属性遵循与标准 $type 相同的类型名称序列化.

    WrappedJsonConverter&lt;T&gt; 作为转换器添加到接口(即IBar),但除此之外,此包装器对不需要转换器的类完全透明,并且也不需要更改 包装 转换器。

    我使用了一个稍微不同的技巧来绕过转换器/序列化器循环(静态字段),但它不需要任何关于正在使用的序列化器设置的知识,并允许 IBar 对象图有子 @987654331 @ 特性。

    对于包装的对象,Json 看起来像:

    "IBarProperty" : {
        "$wrappedType" : "Namespace.ConcreteBar, Namespace",
        "$wrappedValue" : {
            "ConvertedID" : 90,
            "ConvertedPropID" : 70
            ...
        }
    }
    

    The full gist can be found here.

    public class WrappedJsonConverter<T> : JsonConverter<T> where T : class
    {        
        [ThreadStatic]
        private static bool _canWrite = true;
        [ThreadStatic]
        private static bool _canRead = true;
    
        public override bool CanWrite
        {
            get
            {
                if (_canWrite)
                    return true;
    
                _canWrite = true;
                return false;
            }
        }
    
        public override bool CanRead
        {
            get
            {
                if (_canRead)
                    return true;
    
                _canRead = true;
                return false;
            }
        }
    
        public override T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer)
        {
            var jsonObject = JObject.Load(reader);
            JToken token;
            T value;
    
            if (!jsonObject.TryGetValue("$wrappedType", out token))
            {
                //The static _canRead is a terrible hack to get around the serialization loop...
                _canRead = false;
                value = jsonObject.ToObject<T>(serializer);
                _canRead = true;
                return value;
            }
    
            var typeName = jsonObject.GetValue("$wrappedType").Value<string>();
    
            var type = JsonExtensions.GetTypeFromJsonTypeName(typeName, serializer.Binder);
    
            var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanRead);
    
            var wrappedObjectReader = jsonObject.GetValue("$wrappedValue").CreateReader();
    
            wrappedObjectReader.Read();
    
            if (converter == null)
            {
                _canRead = false;
                value = (T)serializer.Deserialize(wrappedObjectReader, type);
                _canRead = true;
            }
            else
            {
                value = (T)converter.ReadJson(wrappedObjectReader, type, existingValue, serializer);
            }
    
            return value;
        }
    
        public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
        {
            var type = value.GetType();
            var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanWrite);
    
            if (converter == null)
            {
                //This is a terrible hack to get around the serialization loop...
                _canWrite = false;
                serializer.Serialize(writer, value, type);
                _canWrite = true;
                return;
            }
    
            writer.WriteStartObject();
            {
                writer.WritePropertyName("$wrappedType");
                writer.WriteValue(type.GetJsonSimpleTypeName());
                writer.WritePropertyName("$wrappedValue");
    
                converter.WriteJson(writer, value, serializer);
            }
            writer.WriteEndObject();
        }
    }
    

    【讨论】:

    • static bool _canWritestatic bool _canRead 的使用不是线程安全的。由于包括asp.net-web-api 在内的多个框架跨线程共享转换器,这可能会导致问题。相反,请按照this answer 中的建议使用[ThreadStatic]ThreadLocal&lt;bool&gt;
    • @dbc 好电话,在我的使用场景(桌面)中这不是问题,而是一个有价值的默认值!谢谢。
    • 与 Alesandr Ivanov 的回答相同的问题:如果正在转换的类型也引用了需要转换器的其他类型,则它将不起作用。您实际上是在禁用转换器的情况下反序列化整个子树。
    • @torvin 你应该自己测试这个答案,它没有那个问题。由于它是通用的,因此静态成员不与其他类型共享。我还使用了接受序列化程序的ToObject 方法。本质上,它对其他类型是透明的,无论它们是否有转换器。
    • @AndrewHanlon 抱歉,我想我不够清楚。这是您的转换器失败的test case
    猜你喜欢
    • 1970-01-01
    • 2014-10-09
    • 2011-12-23
    • 1970-01-01
    • 2019-05-05
    • 2016-08-26
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    相关资源
    最近更新 更多