在Web API中将包含JToken的对象序列化为XML时的循环引用异常 [英] Circular reference exception when serializing an object containing a JToken to XML in Web API
问题描述
在我的数据库中,我有一个包含很多列的表,其中一个包含JSON字符串(对此我无能为力).像这样:
In my database, I have a table with lots of columns and one of them contains a JSON string (I have no control over this). Something like this:
Name Age ExtraData
---- --- ------------------
Bob 31 {c1: "1", c2: "2"} <-- string with JSON
我的Web API端点必须返回XML或JSON,具体取决于请求中的Accept标头. 像这样:
My Web API endpoint must return XML or JSON depending on the Accept headers in the request. Something like this:
JSON:
{
"Name": "Bob",
"Age": 31,
"ExtraData": {
"c1": 1,
"c2": 2
}
}
XML:
<person>
<Name>Bob</Name>
<Age>31</Age>
<ExtraData>
<c1>1</c1>
<c2>2</c2>
</ExtraData>
</person>
为此,我在C#中创建了一个这样的类:
To do this, I have created a class in C# like this:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Object ExtraData { get; set; }
}
从数据库中解析数据时,我将像这样填写ExtraData
:
When parsing the data from database, I will fill in the ExtraData
like this:
personInstance.ExtraData = JsonConvert.DeserializeObject(personTableRow.ExtraData);
当Web API返回JSON时,所有功能均按预期工作.
When the Web API returns JSON, all works as expected.
当Web API返回XML时,它会给出异常:
When the Web API returns XML, it gives an Exception:
"ObjectContent`1"类型未能序列化内容类型"application/xml"的响应主体; charset = utf-8'.
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
内部异常是这样的(不是英语):
The inner exception, is something like this (it is not in English):
Newtonsoft.Json.Linq.JToken有一个循环引用,但不受支持. (O tipo'Newtonsoft.Json.Linq.JToken'和与之相关的数据相反.请考虑修改一个'Newtonsoft.Json.Linq.JToken'的替代定义). >
Newtonsoft.Json.Linq.JToken has a circular reference and that is not supported. (O tipo 'Newtonsoft.Json.Linq.JToken' é um contrato de dados de coleção recursiva que não é suportado. Considere modificar a definição da coleção 'Newtonsoft.Json.Linq.JToken' para remover referências a si mesma.)
有没有办法在没有循环引用的情况下将JSON数据解析为对象?
Is there a way to parse the JSON data to an object without a circular reference?
推荐答案
您遇到了XmlSerializer
的限制.将JSON对象(由括号括起来的无序的名称/值对的集合)反序列化为ac#object
时,Json.NET将创建类型为 JToken.Parent
.因此,您需要将基础object ExtraData
转换为XmlSerializer
可以处理的类型.
You have run into a limitation of the XmlSerializer
. When deserializing a JSON object (which is an unordered set of name/value pairs surrounded by braces) to a c# object
, Json.NET creates an object of type JObject
, and unfortunately XmlSerializer
does not know how to serialize a JObject
. In particular it falls into an infinite recursion trying to serialize the children of JToken.Parent
. Thus, you need to convert the underlying object ExtraData
to a type XmlSerializer
can handle.
但是,使用哪种类型并不明显,因为:
However, it's not obvious what type to use, since:
-
用来表示JSON对象的最自然的c#类型是字典,而
XmlSerializer
不支持字典.
The most natural c# type with which to represent a JSON object is a dictionary, and
XmlSerializer
does not support dictionaries.
XmlSerializer
通过 static 类型发现起作用.必须通过
XmlSerializer
works by static type discovery. All polymorphic subtypes of object
that might be encountered must be declared via [XmlInclude(typof(T))]
. However, if that is done, the XML will include the actual type as an xsi:type
attribute which you don't seem to want in your XML.
您可以做的是利用 [XmlAnyElement]
功能来创建代理属性,以将您的object ExtraData
与 XmlNodeConverter
:
What you can do is to take advantage of the [XmlAnyElement]
functionality to create a surrogate property that converts your object ExtraData
from and to an XElement
using Json.NET's XmlNodeConverter
:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
[XmlIgnore]
[JsonProperty]
public object ExtraData { get; set; }
[XmlAnyElement("ExtraData")]
[JsonIgnore]
public XElement ExtraDataXml
{
get
{
return JsonExtensions.SerializeExtraDataXElement("ExtraData", ExtraData);
}
set
{
ExtraData = JsonExtensions.DeserializeExtraDataXElement("ExtraData", value);
}
}
}
public static class JsonExtensions
{
public static XElement SerializeExtraDataXElement(string name, object extraData)
{
if (extraData == null)
return null;
var token = JToken.FromObject(extraData);
if (token is JValue)
{
return new XElement(name, (string)token);
}
else if (token is JArray)
{
return new JObject(new JProperty(name, token)).ToXElement(false, name, true);
}
else
{
return token.ToXElement(false, name, true);
}
}
public static object DeserializeExtraDataXElement(string name, XElement element)
{
object extraData;
if (element == null)
extraData = null;
else
{
extraData = element.ToJToken(true, name, true);
if (extraData is JObject)
{
var obj = (JObject)extraData;
if (obj.Count == 1 && obj.Properties().First().Name == name)
extraData = obj.Properties().First().Value;
}
if (extraData is JValue)
{
extraData = ((JValue)extraData).Value;
}
}
return extraData;
}
public static XElement ToXElement(this JToken obj, bool omitRootObject, string deserializeRootElementName, bool writeArrayAttribute)
{
if (obj == null)
return null;
using (var reader = obj.CreateReader())
{
var converter = new Newtonsoft.Json.Converters.XmlNodeConverter() { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute };
var jsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = { converter } });
return jsonSerializer.Deserialize<XElement>(reader);
}
}
public static JToken ToJToken(this XElement xElement, bool omitRootObject, string deserializeRootElementName, bool writeArrayAttribute)
{
// Convert to Linq to XML JObject
var settings = new JsonSerializerSettings { Converters = { new XmlNodeConverter { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute } } };
var root = JToken.FromObject(xElement, JsonSerializer.CreateDefault(settings));
return root;
}
}
使用上面的类,我可以反序列化JSON并序列化为XML,结果如下:
Using the class above, I can deserialize your JSON and serialize to XML with the following result:
<Person>
<Name>Bob</Name>
<Age>31</Age>
<ExtraData>
<c1>1</c1>
<c2>2</c2>
</ExtraData>
</Person>
请注意,JSON和XML之间的不一致会导致问题:
Note that there are inconsistencies between JSON and XML that cause problems:
-
JSON基本值是轻"类型的(如字符串,数字,布尔值或null),而XML文本则是完全无类型的.因此,JSON中的数字值(和日期)将作为字符串往返XML.
JSON primitive values are "lightly" typed (as string, number, Boolean or null) whereas XML text is completely untyped. Thus numeric values (and dates) in the JSON get round-tripped to XML as strings.
XML没有数组的概念.因此,其根容器是数组的JSON要求在序列化期间添加一个合成根元素.这会在转换过程中增加一些代码异味.
XML has no concept of an array. Thus JSON whose root container is an array requires a synthetic root element to be added during serialization. This adds some code smell during the conversion process.
XML必须具有单个根元素,而有效的JSON可以由原始值(例如字符串)组成.再次需要在转换过程中使用合成根元素.
XML must have a single root element while valid JSON can consist of a primitive value, such as a string. Again a synthetic root element is required during conversion.
经过轻巧测试的原型小提琴此处,在此我演示了该代码适用于ExtraData
这是JSON对象,字符串数组,单个字符串和null
值.
Lightly tested prototype fiddle here, where I demonstrate that the code works for ExtraData
that is a JSON object, an array of strings, a single string, and a null
value.
这篇关于在Web API中将包含JToken的对象序列化为XML时的循环引用异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!