该项目是Asp.Net Web API Web服务。
我有一个类型层次结构,我需要能够与Json进行序列化,所以我从这个SO:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?获取代码,并将转换器应用到我的层次结构的基类;像这样的东西(这里有伪代码隐藏无关紧要):
[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
{
}
因此,如果我反序列化其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);
}
但是,当其中一个对象被序列化时,我得到JsonSerializationException
:Self referencing loop detected with type 'DerivedType'
。再次 - 如果我删除转换器属性(禁用我的自定义创建),那么它工作正常......
我有一种感觉,这意味着我的序列化代码实际上是在同一个对象上再次触发转换器,而这反过来再次调用序列化器 - 令人作呕。 确认 - 请参阅我的回答
那么我应该在WriteObject
编写哪些代码 哪个代码可以执行相同的'标准'序列化?
答案 0 :(得分:52)
这很有趣......
当我更仔细地查看异常的堆栈跟踪时,我注意到方法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;
}
值得庆幸的是,if
语句中的最后一个条件为我的问题提供了解决方案:我所要做的就是将以下内容添加到从问题中链接的SO中的代码复制的基本转换器中,或者在衍生的一个中:
public override bool CanWrite
{
get
{
return false;
}
}
现在一切正常。
然而,这样做的结果是,如果您打算在对象上进行一些自定义JSON序列化,并且使用转换器 和 注入它,那么您打算在某些或所有情况下回退到标准序列化机制;然后你不能因为你会欺骗框架以为你试图存储一个循环引用。
我确实尝试过操纵ReferenceLoopHandling
成员,但是如果我告诉它Ignore
他们没有被序列化,如果我告诉它保存它们,不出所料,我得到了堆栈溢出。
这可能是Json.Net中的一个错误 - 好吧,这是一个边缘情况,它有可能从宇宙边缘掉下来 - 但如果你确实发现自己处于这种情况,那么你就是有点卡住了!
答案 1 :(得分:6)
我使用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);
}
}
答案 2 :(得分:2)
我自己也遇到过这种情况,我沮丧地拔出了头发!
要解决这个问题,以下内容对我有用,但由于我错过了CanWrite
解决方案,这是一个更复杂的解决方法。
JsonConverter
属性。WriteJson
方法中,将值转换为虚拟类型,然后将该类型序列化。例如,这与我原来的类相似:
[JsonConverter(typeof(MyResponseConverter))]
public class MyResponse
{
public ResponseBlog blog { get; set; }
public Post[] posts { get; set; }
}
副本如下所示:
public class FakeMyResponse
{
public ResponseBlog blog { get; set; }
public Post[] posts { get; set; }
public FakeMyResponse(MyResponse response)
{
blog = response.blog;
posts = response.posts;
}
}
WriteJson是:
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
if (CanConvert(value.GetType()))
{
FakeMyResponse response = new FakeMyResponse((MyResponse)value);
serializer.Serialize(writer, response);
}
}
修改强>
OP指出使用Expando可能是另一种可能的解决方案。这很有效,省去了创建新类的麻烦,尽管DLR支持需要Framework 4.0或更高版本。方法是创建一个新的dynamic
ExpandoObject
,然后直接在WriteJson
方法中初始化其属性以创建副本,例如:
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
if (CanConvert(value.GetType()))
{
var response = (MyResponse)value;
dynamic fake = new System.Dynamic.ExpandoObject();
fake.blog = response.blog;
fake.posts = response.posts;
serializer.Serialize(writer, fake);
}
}
答案 3 :(得分:2)
我和父/子收藏品有同样的问题,发现那个帖子解决了我的问题。我只想显示父集合项列表,并且不需要任何子数据,因此我使用以下内容并且工作正常:
JsonConvert.SerializeObject(ResultGroups, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
它还引用了Json.NET codplex页面:
答案 4 :(得分:1)
IMO,这是图书馆的严重限制。解决方案很简单,虽然我承认它并没有快速找到我。解决方案是设置:
.ReferenceLoopHandling = ReferenceLoopHandling.Serialize
,正如所记录的那样,将消除自引用错误并将其替换为堆栈溢出。在我的情况下,我需要写入功能,因此将CanWrite设置为false不是一个选项。最后,我只是设置了一个标志来保护CanConvert调用,当我知道对序列化器的调用正在导致(无限的)递归时:
Public Class ReferencingObjectConverter : Inherits JsonConverter
Private _objects As New HashSet(Of String)
Private _ignoreNext As Boolean = False
Public Overrides Function CanConvert(objectType As Type) As Boolean
If Not _ignoreNext Then
Return GetType(IElement).IsAssignableFrom(objectType) AndAlso Not GetType(IdProperty).IsAssignableFrom(objectType)
Else
_ignoreNext = False
Return False
End If
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Try
If _objects.Contains(CType(value, IElement).Id.Value) Then 'insert a reference to existing serialized object
serializer.Serialize(writer, New Reference With {.Reference = CType(value, IElement).Id.Value})
Else 'add to my list of processed objects
_objects.Add(CType(value, IElement).Id.Value)
'the serialize will trigger a call to CanConvert (which is how we got here it the first place)
'and will bring us right back here with the same 'value' parameter (and SO eventually), so flag
'the CanConvert function to skip the next call.
_ignoreNext = True
serializer.Serialize(writer, value)
End If
Catch ex As Exception
Trace.WriteLine(ex.ToString)
End Try
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Throw New NotImplementedException()
End Function
Private Class Reference
Public Property Reference As String
End Class
End Class
答案 5 :(得分:0)
这可能对某人有所帮助,但在我的情况下,我试图覆盖Equals方法以将我的对象视为值类型。在我的研究中,我发现JSON.NET不是这样的:
答案 6 :(得分:0)
我是一个简单的错误,与此主题的解决方案无关。
此主题是google中的第一页,因此我发布此处以防其他人遇到与我相同的问题。
dynamic table = new ExpandoObject();
..
..
table.rows = table; <<<<<<<< I assigned same dynamic object to itself.