如何使用数组项的父节点从JSON生成XML [英] How to generate XML from JSON with parent node of array items

查看:65
本文介绍了如何使用数组项的父节点从JSON生成XML的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个与C#对象图及其JSON表示紧密一致的XML文档,但是在XML中使用列表表示存在困难.给定这张图

public class X
{
    public List<A> Aa { get; set; }
}

public class A
{
    public int B;
    public bool C;
}

我从上面获取了JSON,并尝试了几种转换方式:

var json = @"{""Aa"":[{""B"":186,""C"":true},{""B"":9,""C"":false},{""B"":182,""C"":true}]}";
var xml = JsonConvert.DeserializeXNode(json, typeof(T).Name, false);
var xml2 = JsonToXml(json);

这为xml生成了以下内容(没有Aa容器节点"):

<X>
  <Aa><B>186</B><C>true</C></Aa>
  <Aa><B>9</B><C>false</C></Aa>
  <Aa><B>182</B><C>true</C></Aa>
</X>

对于xml2(具有容器"节点,但有一些额外的噪音):

<root type="object">
  <Aa type="array">
    <item type="object">
      <B type="number">186</B>
      <C type="boolean">true</C>
    </item>
    <item type="object">
      <B type="number">9</B>
      <C type="boolean">false</C>
    </item>
    <item type="object">
      <B type="number">182</B>
      <C type="boolean">true</C>
    </item>
  </Aa>
</root>

用于产生xml2值的方法来自使用.NET Framework的不同方法:

    XDocument JsonToXml(string jsonString)
    {
        using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(jsonString)))
        {
            var quotas = new XmlDictionaryReaderQuotas();
            return XDocument.Load(JsonReaderWriterFactory.CreateJsonReader(stream, quotas));
        }
    }

我想生产的是

<X>
  <Aa>
    <A><B>186</B><C>true</C></A>
    <A><B>9</B><C>false</C></A>
    <A><B>182</B><C>true</C></A>
  </Aa>
</X>

我尝试更改writeArrayAttribute参数rel ="nofollow noreferrer"> DeserializeXDocument 确实如此,但这也不起作用. 在JSON和XML之间进行转换的文档无济于事. >

如何生成包含Aa父节点中项目的压缩版本?这将需要一些自定义解串器吗?

原始JSON是通过

创建的

var json = JsonConvert.SerializeObject(new X { etc }, Formatting.None, settings);

解决方案

问题.

之所以会遇到麻烦,是因为有两种将集合序列化为XML的常用方法,而Json.NET仅支持其中一种自动将JSON转换为XML的方法.

具体来说,当将c#集合序列化为XML(带有XmlSerializer)时,可以使用或不使用外部容器元素来对集合进行序列化.前者如下所示:

<X>
  <Aa>
    <A>
      <B>186</B>
      <C>true</C>
    </A>
    <A>
      <B>9</B>
      <C>false</C>
    </A>
  </Aa>
</X>

后者看起来像:

<X>
  <Aa>
    <B>186</B>
    <C>true</C>
  </Aa>
  <Aa>
    <B>9</B>
    <C>false</C>
  </Aa>
</X>

当Json.NET将JSON数组转换为XML元素时,它使用该数组的第二种格式,因为JSON仅包含一个属性名称,而两级XML格式同时需要内部和内部外部元素名称. IE.在您的JSON中:

{"Aa":[{"B":186,"C":true},{"B":9,"C":false}]}

仅出现名称"Aa".名称"A"从未使用过,因此DeserializeXNode()不知道要插入它.这使第二种格式成为规范转换的直接选择,而您需要第一种.

解决方案.

要从JSON数组生成两级XML集合,您需要在转换之前插入合成JSON对象,或者在转换之后插入合成XML元素.对于前者,可以通过将JSON字符串解析为中间的JToken并按如下所示对其进行修改来完成:

var jObject = JObject.Parse(json);

jObject.SelectTokens("Aa").WrapWithObjects("A");

var finalXml = jObject.ToXElement(typeof(X).Name, false);

使用扩展方法:

public static class JsonExtensions
{
    public static void WrapWithObjects(this IEnumerable<JToken> values, string name)
    {
        foreach (var value in values.ToList())
        {
            var newParent = new JObject();
            if (value.Parent != null)
                value.Replace(newParent);
            newParent[name] = value;
        }
    }

    public static XElement ToXElement(this JObject obj, string deserializeRootElementName = null, bool writeArrayAttribute = false)
    {
        if (obj == null)
            return null;
        using (var reader = obj.CreateReader())
            return JsonExtensions.DeserializeXElement(reader, deserializeRootElementName, writeArrayAttribute);
    }

    static XElement DeserializeXElement(JsonReader reader, string deserializeRootElementName, bool writeArrayAttribute)
    {
        var converter = new Newtonsoft.Json.Converters.XmlNodeConverter() { DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute };
        var jsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = new JsonConverter[] { converter } });
        return jsonSerializer.Deserialize<XElement>(reader);
    }
}

或者,您可以通过将Aa标记为

This produced the following for xml (no Aa "container node"):

<X>
  <Aa><B>186</B><C>true</C></Aa>
  <Aa><B>9</B><C>false</C></Aa>
  <Aa><B>182</B><C>true</C></Aa>
</X>

And for xml2 (has "container" node, but some extra noise):

<root type="object">
  <Aa type="array">
    <item type="object">
      <B type="number">186</B>
      <C type="boolean">true</C>
    </item>
    <item type="object">
      <B type="number">9</B>
      <C type="boolean">false</C>
    </item>
    <item type="object">
      <B type="number">182</B>
      <C type="boolean">true</C>
    </item>
  </Aa>
</root>

The method used to produce the value for xml2 comes from a different approach using the .NET Framework:

    XDocument JsonToXml(string jsonString)
    {
        using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(jsonString)))
        {
            var quotas = new XmlDictionaryReaderQuotas();
            return XDocument.Load(JsonReaderWriterFactory.CreateJsonReader(stream, quotas));
        }
    }

What I want to produce is

<X>
  <Aa>
    <A><B>186</B><C>true</C></A>
    <A><B>9</B><C>false</C></A>
    <A><B>182</B><C>true</C></A>
  </Aa>
</X>

I have tried changing the writeArrayAttribute parameter of DeserializeXDocument to true, but that doesn't work either. The documentation for converting between JSON and XML does not help.

How can I produce the compact version that contains the items in a parent Aa node? Is this going to require some custom deserializer?

The original JSON was created via

var json = JsonConvert.SerializeObject(new X { etc }, Formatting.None, settings);

解决方案

The Problem.

Your difficulty arises because there are two common ways to serialize a collection to XML, and Json.NET only supports automatic JSON-to-XML conversion for one of them.

Specifically, when serializing a c# collection to XML (with, say, XmlSerializer), the collection can be serialized either with, or without, an outer container element. The former looks like the following:

<X>
  <Aa>
    <A>
      <B>186</B>
      <C>true</C>
    </A>
    <A>
      <B>9</B>
      <C>false</C>
    </A>
  </Aa>
</X>

While the latter looks like:

<X>
  <Aa>
    <B>186</B>
    <C>true</C>
  </Aa>
  <Aa>
    <B>9</B>
    <C>false</C>
  </Aa>
</X>

When Json.NET converts a JSON array to XML elements, it uses the second format for the array, since the JSON only contains one property name while the two-level XML format requires both inner and outer element names. I.e. in your JSON:

{"Aa":[{"B":186,"C":true},{"B":9,"C":false}]}

Only the name "Aa" appears. The name "A" never does, so DeserializeXNode() cannot know to insert it. This makes the second format the straightforward choice for canonical conversion, whereas you require the first.

The Solution.

To generate a two-level XML collection from a JSON array, you'll need to either insert synthetic JSON objects before conversion, or synthetic XML elements afterwards. For the former, this can be done by parsing the JSON string to an intermediate JToken, and modifying it as follows:

var jObject = JObject.Parse(json);

jObject.SelectTokens("Aa").WrapWithObjects("A");

var finalXml = jObject.ToXElement(typeof(X).Name, false);

Using the extension methods:

public static class JsonExtensions
{
    public static void WrapWithObjects(this IEnumerable<JToken> values, string name)
    {
        foreach (var value in values.ToList())
        {
            var newParent = new JObject();
            if (value.Parent != null)
                value.Replace(newParent);
            newParent[name] = value;
        }
    }

    public static XElement ToXElement(this JObject obj, string deserializeRootElementName = null, bool writeArrayAttribute = false)
    {
        if (obj == null)
            return null;
        using (var reader = obj.CreateReader())
            return JsonExtensions.DeserializeXElement(reader, deserializeRootElementName, writeArrayAttribute);
    }

    static XElement DeserializeXElement(JsonReader reader, string deserializeRootElementName, bool writeArrayAttribute)
    {
        var converter = new Newtonsoft.Json.Converters.XmlNodeConverter() { DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute };
        var jsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = new JsonConverter[] { converter } });
        return jsonSerializer.Deserialize<XElement>(reader);
    }
}

Alternatively, you could tell XmlSerializer to (de)serialize the Aa list without a container element by marking it with [XmlElement]:

public class X
{
    [XmlElement]
    public List<A> Aa { get; set; }
}

Now the xml generated by JsonConvert.DeserializeXNode will be deserializable directly.

这篇关于如何使用数组项的父节点从JSON生成XML的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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