【问题标题】:Recursively call JsonSerializer in a JsonConverter在 JsonConverter 中递归调用 JsonSerializer
【发布时间】:2013-04-18 14:33:12
【问题描述】:

我正在写一个JsonConverter 来执行一些我需要在读/写时完成的转换任务。特别是,我将采用现有的序列化行为,并在写入/读取时添加一些附加属性。

JsonConverter 内部,我想利用传递的JsonSerializer 实例来执行大部分转换功能。但是,当我这样做时,我最终会进入一个递归循环,其中序列化程序调用我的转换器,然后调用序列化程序,再调用转换器等等。

我见过人们使用JsonConvert.SerializeObject,从序列化程序实例中传入所有转换器除了this。但是,这对我不起作用,因为它绕过了我在序列化程序上完成的所有其他自定义,例如自定义合同解析器和 DateTime 处理。

有没有办法:

  1. 使用传递给我的序列化程序实例,但以某种方式排除我的转换器,或者
  2. 克隆传递给我的序列化程序(无需手动构建新序列化程序并逐个属性地复制它)并删除我的转换器?

【问题讨论】:

    标签: c# .net json serialization json.net


    【解决方案1】:

    这是一个很常见的问题。使用“JsonConvert.SerializeObject”不是一个坏主意。但是,在某些情况下(通常是集合)可以使用的一个技巧是在写入时强制转换为接口,并在读取时反序列化为简单的派生。

    下面是一个简单的转换器,它处理可能被序列化为一组 KVP 而不是看起来像一个对象的字典(在这里显示我的年龄:))

    注意“WriteJson”转换为 IDictionary 并且“ReadJson”使用“DummyDictionary”。您最终得到了正确的结果,但使用了传递的序列化程序而不会导致递归。

    /// <summary>
    /// Converts a <see cref="KeyValuePair{TKey,TValue}"/> to and from JSON.
    /// </summary>
    public class DictionaryAsKVPConverter<TKey, TValue> : JsonConverter
    {
        /// <summary>
        /// Determines whether this instance can convert the specified object type.
        /// </summary>
        /// <param name="objectType">Type of the object.</param>
        /// <returns>
        ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
        /// </returns>
        public override bool CanConvert(Type objectType)
        {
            if (!objectType.IsValueType && objectType.IsGenericType)
                return (objectType.GetGenericTypeDefinition() == typeof(Dictionary<,>));
    
            return false;
        }
    
        /// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        /// <param name="serializer">The calling serializer.</param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var dictionary = value as IDictionary<TKey, TValue>;
            serializer.Serialize(writer, dictionary);
        }
    
        /// <summary>
        /// Reads the JSON representation of the object.
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            Dictionary<TKey, TValue> dictionary;
    
            if (reader.TokenType == JsonToken.StartArray)
            {
                dictionary = new Dictionary<TKey, TValue>();
                reader.Read();
                while (reader.TokenType == JsonToken.StartObject)
                {
                    var kvp = serializer.Deserialize<KeyValuePair<TKey, TValue>>(reader);
                    dictionary[kvp.Key] = kvp.Value;
                    reader.Read();
                }
            }
            else if (reader.TokenType == JsonToken.StartObject)
                // Use DummyDictionary to fool JsonSerializer into not using this converter recursively
                dictionary = serializer.Deserialize<DummyDictionary>(reader);
            else
                dictionary = new Dictionary<TKey, TValue>();
    
            return dictionary;
        }
    
        /// <summary>
        /// Dummy to fool JsonSerializer into not using this converter recursively
        /// </summary>
        private class DummyDictionary : Dictionary<TKey, TValue> { }
    }
    

    【讨论】:

    • 跟进:stackoverflow.com/q/60254867/156755 如果你想看看
    • 有什么特殊原因导致您没有在CanConvert 中明确检查DummyDictionary
    • 请注意 DummyDictionary 是 private。它永远不能被传入。 Dummy 的实例被转换/返回为字典,这是 CanConvert 检查的。所以,我觉得可以吗?你在看什么情况?
    • 不是具体情况;写if (objectType == typeof(DummyDictionary)) {return false;} 对我来说似乎更清楚一点,然后执行其余的检查。这将允许增加一点灵活性:例如实现IDIctionary&lt;TKey, TValue&gt; 的任何类型,或包括从Dictionary&lt;TKey, TValue&gt; 继承的其他类型。
    • 我想你不会想要那个。您仍然希望能够序列化先前反序列化的结果。你的改变会阻止这种情况。更改为 IDictionary 是明智的。尽管自定义 impl 非常罕见。该代码当时只是从一个项目中剪切和粘贴的。为我们工作:)
    【解决方案2】:

    您确实可以使用传递给转换器的序列化程序实例,并排除当前转换器。但是,这不是线程安全的(请参阅此答案的 cmets)

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Converters.Remove(this);
        serializer.Serialize(writer, value);
        serializer.Converters.Add(this);
    }
    

    【讨论】:

    • 这不是线程安全的。由于几乎所有网络规模的工作都是多线程的,这实际上并没有帮助。
    • @DavidPfeffer 似乎确实需要考虑到这一点。我认为每次需要序列化或反序列化某些东西时制作一个新的序列化程序可以解决这个问题,但也许对性能的影响太大而无法使该解决方案可行......
    【解决方案3】:

    抱歉,我可能有点困惑。我使用这些方法来序列化我的对象:

    using System;
    using Newtonsoft.Json;
    
    namespace Utilities
    {
        public static class serializer
        {
            public static string SerializeObject(object objectModel) {
                return JsonConvert.SerializeObject(objectModel);
            }
            public static object DeserializeObject<T>(string jsonObject)
            {
                try
                {
                    return JsonConvert.DeserializeObject<T>(jsonObject);
                }
                catch (Exception ex) { return null; }
                
            }
        }
    }

    我使用了这段代码:

    userLoged = (modelUser)serializer.DeserializeObject&lt;modelUser&gt;((string)Session["userLoged"]);

    我希望这会有所帮助。

    【讨论】:

    • 这没有回答所提出的问题。问题不是“如何使用JsonConvert 序列化我的对象?”它询问如何避免自定义JsonConverter 内部的递归循环。请注意,JsonConvertJsonConverter 在 Json.Net 中是两个完全不同的类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 2016-03-21
    • 1970-01-01
    • 2019-10-18
    相关资源
    最近更新 更多