在Web API中将包含JToken的对象序列化为XML时的循环引用exception
在我的数据库中,我有一个包含许多列的表,其中一个包含一个JSON字符串(我无法控制它)。 像这样的东西:
Name Age ExtraData ---- --- ------------------ Bob 31 {c1: "1", c2: "2"} <-- string with JSON
我的Web API端点必须返回XML或JSON,具体取决于请求中的Accept标头。 像这样的东西:
JSON:
{ "Name": "Bob", "Age": 31, "ExtraData": { "c1": 1, "c2": 2 } }
XML:
Bob 31 1 2
为此,我在C#中创建了一个类,如下所示:
public class Person { public string Name { get; set; } public int Age { get; set; } public Object ExtraData { get; set; } }
解析数据库中的数据时,我将填写ExtraData
如下所示:
personInstance.ExtraData = JsonConvert.DeserializeObject(personTableRow.ExtraData);
当Web API返回JSON时,所有工作都按预期工作。
当Web API返回XML时,它会给出一个exception:
‘ObjectContent`1’类型无法序列化内容类型’application / xml的响应主体; 字符集= UTF-8’ 。
内部exception是这样的(它不是英文):
Newtonsoft.Json.Linq.JToken有一个循环引用,不支持。 (Otipo’Newtonsoft.Json.Linq.JToken’éumcontrato de dadosdecoleçãorecursivaquenãoésurportado。考虑修改一个definiçãodacoleção’Newtonsoft.Json.Linq.JToken’pararemoverreferênciasassima。)
有没有办法在没有循环引用的情况下将JSON数据解析为对象?
您遇到了XmlSerializer
的限制。 当将JSON对象(由大括号包围的无序的名称/值对集合)反序列化为ac# object
,Json.NET会创建一个JObject
类型的对象,遗憾的是XmlSerializer
不知道如何序列化JObject
。 特别是它会进入无限递归,试图序列化JToken.Parent
。 因此,您需要将底层object ExtraData
转换为XmlSerializer
可以处理的类型。
但是,使用什么类型并不明显,因为:
-
用于表示JSON对象的最自然的c#类型是字典,
XmlSerializer
不支持字典 。 -
XmlSerializer
通过静态类型发现工作。 必须通过[XmlInclude(typof(T))]
声明可能遇到的所有object
多态子类型。 但是,如果这样做,XML将包含实际类型作为xsi:type
属性 ,您在XML中似乎不需要该属性 。
你可以做的是利用[XmlAnyElement]
function创建一个代理属性,使用Json.NET的XmlNodeConverter
将object ExtraData
转换为XElement
:
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(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,结果如下:
Bob 31 1 2
请注意,JSON和XML之间存在导致问题的不一致:
-
JSON原始值是“轻微”类型(如字符串,数字,布尔值或null),而XML文本是完全无类型的。 因此,JSON中的数值(和日期)作为字符串进行往返XML。
-
XML没有数组的概念。 因此,其根容器是数组的JSON需要在序列化期间添加合成根元素。 这会在转换过程中增加一些代码味道。
-
XML必须具有单个根元素,而有效的JSON可以包含原始值,例如字符串。 转换期间还需要合成的根元素。
这里经过轻度测试的原型小提琴,我演示代码适用于ExtraData
,它是一个JSON对象,一个字符串数组,一个字符串和一个null
值。