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
方法调用的来源.
所以看起来串行器正在做:
因此,在这种情况下,Json.Net正在调用我的转换器,而转换器又调用Json.Net序列化器,然后它会爆炸,因为它看到它已经序列化了传递给它的对象!
在DLL打开ILSpy(是的,我知道它是开源的-但我想要的"来电"的功能!),并且调用堆栈向上移动SerializeConvertable
到JsonSerializerInternalWriter.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中的一个错误 - 好吧,这是一个边缘情况,它有可能从宇宙边缘掉下来 - 但如果你确实发现自己处于这种情况,那么你就会陷入困境!
小智 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)