Amir Popovich Amir Popovich - 6 months ago 29
JSON Question

Is there anyway I can prevent the creation of an extra "object node" during converting a xml array to json using json.net?

I'm trying to convert a xml to json using

Json.net
(JsonConvert.SerializeXNode).

There is a problem when you try to convert between an xml and a json if you don't use some kind of schema (xsd), since you can't really identify the difference between an xml collection with a single element to a regular object.

Example:

<Drivers>
<Driver>
<Name>MyName</Name>
</Driver>
</Drivers>


Will be converted into:

"Drivers":{ "Driver": { "Name": "MyName" } }


since nobody tells the serializer that Drivers is a collection with a single object and it thinks it's just a regular object.

Json.net
has a work around for this using the json:Array='true' tagging.

Everything works great when you tag the arrays, but it creates an extra middle object (Driver):

"Drivers": [{"Driver":{"Name": "MyName"}}]


Now I understand why this node is created, but I'm trying to find a way to bypass that creating. I would like to get this result:

"Drivers": [{"Name": "MyName"}]


Does anyone have any idea how to do something like this?

dbc dbc
Answer

Json.NET will not automatically convert a collection serialized with an outer container element (e.g., one generated with [XmlArray] attached) to a single JSON array. Instead, you will need to manually flatten the unwanted level of nesting by pre-processing the XML with LINQ-to-XML or post-processing with LINQ-to-JSON.

Since you are already preprocessing your XML to add the json:Array='true' attribute, adding some additional preprocessing would seem to be most straightforward. First, introduce the following extension method:

public static class XNodeExtensions
{
    /// <summary>
    /// Flatten a two-level collection with an outer container element to a one-level collection 
    /// in preparation for conversion to JSON using Json.NET
    /// </summary>
    /// <param name="parents">The outer container elements.</param>
    /// <param name="childName">The inner element name.  If null, flatten all children.</param>
    /// <param name="newChildName">The new element name.  If null, use the parent name.</param>
    public static void FlattenCollection(this IEnumerable<XElement> parents, XName childName = null, XName newChildName = null)
    {
        if (parents == null)
            throw new ArgumentNullException();

        XNamespace json = @"http://james.newtonking.com/projects/json";
        XName isArray = json + "Array";

        foreach (var parent in parents.ToList())
        {
            if (parent.Parent == null)
                continue; // Removed or root
            foreach (var child in (childName == null ? parent.Elements() : parent.Elements(childName)).ToList())
            {
                child.Remove();
                child.Name = newChildName ?? parent.Name;
                child.Add(new XAttribute(isArray, true));
                parent.Parent.Add(child);
            }
            if (!parent.HasElements)
                parent.Remove();
        }
    }
}

You can now do:

var xnode = XDocument.Parse(xml);
xnode.Descendants("Drivers").FlattenCollection();
var json = JsonConvert.SerializeXNode(xnode, Formatting.Indented, true);

And get the JSON you want. Sample fiddle.

Note that <Drivers> cannot be the root element to do this, since an XML document must have exactly one root node.