在Web API中将包含JToken的对象序列化为XML时的循环引用异常 [英] Circular reference exception when serializing an object containing a JToken to XML in Web API

查看:87
本文介绍了在Web API中将包含JToken的对象序列化为XML时的循环引用异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的数据库中,我有一个包含很多列的表,其中一个包含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:

  • The most natural c# type with which to represent a JSON object is a dictionary, and XmlSerializer does not support dictionaries.

XmlSerializer通过 static 类型发现起作用.必须通过 属性,您似乎不需要XML.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆