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.