在JsonConverter中递归调用JsonSerializer

27

我正在编写一个JsonConverter来执行一些读/写转换任务。具体来说,我正在采用现有的序列化行为,在写操作中添加一些附加属性/在读取操作中读取这些附加属性。

JsonConverter内部,我想利用传递的JsonSerializer实例来执行大部分的转换功能。然而,当我这样做时,就会陷入一个递归循环,其中序列化器调用我的转换器,我的转换器调用序列化器,序列化器再次调用我的转换器,以此类推。

我见过有人这样做,即使用JsonConvert.SerializeObject,将来自序列化程序实例的所有转换器传递进去,除了this。但是,这对我来说行不通,因为它绕过了我在序列化程序上进行的所有其他定制,比如自定义合同解析器和DateTime处理。

我是否可以:

  1. 使用传递给我的序列化程序实例,但以某种方式排除我的转换器;或者
  2. 克隆传递给我的序列化程序(而不是手动构造一个新的并逐个复制它的属性),并删除我的转换器?
4个回答

9

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

下面是一个简单的转换器,它处理可能已经被序列化为KVP集合而不像一个对象的字典(显示我的年龄:))

请注意,“WriteJson”强制转换为IDictionary<K,V>,“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> { }
}

跟进:如果您有兴趣,请查看 https://dev59.com/kUUFtIcB2Jgan1znN1jW - Basic
你为什么在CanConvert中没有明确检查DummyDictionary的特定原因吗? - Zev Spitz
请注意,DummyDictionary是“private”的。它永远不能被传递。Dummy的实例被强制转换/返回为字典,这就是CanConvert检查的内容。 所以,我认为没问题了?你在看哪种情况? - AndyPook
没有特定的情况;对我来说,写if (objectType == typeof(DummyDictionary)) {return false;}似乎更清晰一些,然后再执行其余的检查。这将允许添加更多的灵活性:例如,任何实现IDIctionary<TKey, TValue>的类型,或包括继承自Dictionary<TKey, TValue>的其他类型。 - Zev Spitz
我认为你不会想要那样做。你仍然希望能够序列化先前反序列化的结果。你的更改将阻止这一点。 改为使用IDictionary可能是明智的选择。尽管自定义实现相当罕见。 当时,该代码只是从一个项目中复制并粘贴而来。对我们有用 :) - AndyPook

1
可以在一次调用Serialize时禁用自己的转换器。即使转换器被重入和/或多线程使用,这也是可行的。
class MyConverter : JsonConverter
{
  [ThreadStatic]
  private static bool Disabled;

  public override bool CanConvert(Type objectType) =>
    Disabled ? (Disabled = false) : the_type_condition;

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  { ...
    try
    { Disabled = true;
      serializer.Serialize(writer, value); // will not invoke WriteJson recursively
    } finally
    { Disabled = false;
    }
    ...
  }
}

-4

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

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

这不是线程安全的。由于几乎所有的网络规模工作都是多线程的,所以这实际上并没有帮助。 - David Pfeffer
2
@DavidPfeffer 看起来这确实是需要考虑的事情。我认为每次需要序列化或反序列化时创建一个新的序列化器会解决这个问题,但也许对性能的影响太大,使得这种解决方案不可行... - B. Ball

-9

抱歉,也许我有点混淆了。我使用这些方法来序列化我的对象:

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<modelUser>((string)Session["userLoged"]);

希望这对你有所帮助。


2
这并没有回答所问的问题。问题不是“如何使用JsonConvert序列化对象?”,而是如何避免在自定义JsonConverter内部出现递归循环。请注意,JsonConvertJsonConverter是Json.Net中两个完全不同的类。 - Brian Rogers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接