自定义JsonConverter(Web API)中的Json.Net JsonSerializer中的自引用循环

And*_*tan 39 c# json.net asp.net-web-api

该项目是Asp.Net Web API Web服务.

我有一个类型层次结构,我需要能够序列化到JSON和从JSON序列化,所以我从这个SO中获取代码:如何在JSON.NET中实现自定义JsonConverter来反序列化基类对象的列表?,并将转换器应用于我的层次结构的基类; 像这样的东西(这里有伪代码隐藏无关紧要):

[JsonConverter(typeof(TheConverter))]
public class BaseType
{
    // note the base of this type here is from the linked SO above
    private class TheConverter : JsonCreationConverter<BaseType>
    {
        protected override BaseType Create(Type objectType, JObject jObject)
        {
            Type actualType = GetTypeFromjObject(jObject); /*method elided*/
            return (BaseType)Activator.CreateInstance(actualType);
        }
    }
}

public class RootType
{
    public BaseType BaseTypeMember { get; set; }
}

public class DerivedType : BaseType
{

}
Run Code Online (Sandbox Code Playgroud)

所以,如果我反序列化RootType实例,其BaseTypeMember等于实例DerivedType,然后将它反序列化回到该类型的实例.

对于记录,这些JSON对象包含一个'$type'包含虚拟类型名称(不是完整的.Net类型名称)的字段,因此我可以同时支持JSON中的类型,同时确切地控制哪些类型可以序列化和反序列化.

现在,这非常适合从请求中反序列化值; 但我有序列化的问题.如果你看一下链接的SO,以及从顶部答案链接的Json.Net讨论,你会发现我使用的基本代码完全适用于反序列化; 其使用示例显示了手动创建序列化程序.由此JsonConverter引入的实现JsonCreationConverter<T>简单地抛出了一个NotImplementedException.

现在,由于Web API为请求使用单个格式化程序的方式,我需要在WriteObject方法中实现"标准"序列化.

我必须强调,在开始我的项目的这一部分之前,我已经正确地序列化了所有内容而没有错误.

所以我这样做了:

public override void WriteJson(JsonWriter writer, 
  object value, 
  JsonSerializer serializer)
{
    serializer.Serialize(writer, value);
}
Run Code Online (Sandbox Code Playgroud)

但我得到一个JsonSerializationException:Self referencing loop detected with type 'DerivedType',当其中一个对象被序列化时.再次 - 如果我删除转换器属性(禁用我的自定义创建),那么它工作正常...

我有一种感觉,这意味着我的序列化代码实际上是在同一个对象上再次触发转换器,而这又反过来调用了序列化器 - 令人作呕.确认 - 看我的答案

那么什么样的代码应该我在写WriteObject那会做同样的"标准"序列化的作品?

And*_*tan 53

这很有趣......

当我更仔细地查看异常的堆栈跟踪时,我注意到该方法JsonSerializerInternalWriter.SerializeConvertable在那里两次,实际上它是一个离堆栈顶部的方法 - 调用JsonSerializerInternalWriter.CheckForCircularReference- 这反过来又抛出了异常.然而,它也是我自己的转换器Write方法调用的来源.

所以看起来串行器正在做:

  • 1)如果对象有转换器
    • 1a)抛出循环参考
    • 1b)调用转换器的Write方法
  • 2)否则
    • 2a)使用内部序列化器

因此,在这种情况下,Json.Net正在调用我的转换器,而转换器又调用Json.Net序列化器,然后它会爆炸,因为它看到它已经序列化了传递给它的对象!

在DLL打开ILSpy(是的,我知道它是开源的-但我想要的"来电"的功能!),并且调用堆栈向上移动SerializeConvertableJsonSerializerInternalWriter.SerializeValue,检测是否应该使用一个转换器,临近开始时找到的代码:

if (((jsonConverter = ((member != null) ? member.Converter : null)) != null 
   || (jsonConverter = ((containerProperty != null) ? containerProperty.ItemConverter 
                                                    : null)) != null 
   || (jsonConverter = ((containerContract != null) ? containerContract.ItemConverter 
                                                    : null)) != null 
   || (jsonConverter = valueContract.Converter) != null 
   || (jsonConverter = 
       this.Serializer.GetMatchingConverter(valueContract.UnderlyingType)) != null 
   || (jsonConverter = valueContract.InternalConverter) != null) 
   && jsonConverter.CanWrite)
{
    this.SerializeConvertable(writer, jsonConverter, value, valueContract, 
                              containerContract, containerProperty);
    return;
}
Run Code Online (Sandbox Code Playgroud)

值得庆幸的是,if声明中的最后一个条件为我的问题提供了解决方案:我所要做的就是将以下内容添加到从问题中链接的SO中的代码复制的基本转换器中,或者在派生的转换器中:

public override bool CanWrite
{
    get
    {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在一切正常.

然而,这样做的结果是,如果您打算在对象上进行一些自定义JSON序列化,并且您正在使用转换器注入它,并且您打算在某些或所有情况下回退到标准序列化机制; 然后你不能,因为你会欺骗框架认为你试图存储循环引用.

我确实尝试过操纵该ReferenceLoopHandling成员,但如果我告诉Ignore他们那么没有任何序列化,如果我告诉它保存它们,不出所料,我得到了一个堆栈溢出.

这可能是Json.Net中的一个错误 - 好吧,这是一个边缘情况,它有可能从宇宙边缘掉下来 - 但如果你确实发现自己处于这种情况,那么你就会陷入困境!

  • 我不得不说这不是一个边缘情况,并且有很多与自定义JsonConverters这个问题相关的线程.我很欣赏这篇文章.谢谢! (2认同)

小智 7

我使用Newtonsoft.Json的4.5.7.15008版本遇到了这个问题.我尝试了这里提供的所有解决方案以及其他一些解决方案.我使用下面的代码解决了这个问题.基本上你可以使用另一个JsonSerializer来执行序列化.创建的JsonSerializer没有任何已注册的转换器,因此将避免重新进入/例外.如果使用其他设置或ContractResolver,则需要在创建的序列化上手动设置它们:可以将一些构造函数参数添加到CustomConverter类以适应这种情况.

    public class CustomConverter : JsonConverter
    {
        /// <summary>
        /// Use a privately create serializer so we don't re-enter into CanConvert and cause a Newtonsoft exception
        /// </summary>
        private readonly JsonSerializer noRegisteredConvertersSerializer = new JsonSerializer();

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            bool meetsCondition = false; /* add condition here */
            if (!meetsCondition)
                writer.WriteNull();
            else
                noRegisteredConvertersSerializer.Serialize(writer, value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override bool CanConvert(Type objectType)
        {
            // example: register accepted conversion types here
            return typeof(IDictionary<string, object>).IsAssignableFrom(objectType);
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 这是解决重入问题的简单解决方案,它也允许回退-像这样:) (2认同)