问题
I have some autogenerated xmls where some parts of the xml may have multiple rows and some may not. The result is that if there is one row a single json node is returned and if I have multiple rows an array with json nodes are returned.
The xmls may look like this
<List>
<Content>
<Row Index="0">
<Title>Testing</Title>
<PercentComplete>0</PercentComplete>
<DueDate/>
<StartDate/>
</Row>
</Content>
</List>
Or with multiple rows
<List>
<Content>
<Row Index="0">
<Title>Update Documentation</Title>
<PercentComplete>0.5</PercentComplete>
<DueDate>2013-01-31 00:00:00</DueDate>
<StartDate>2013-01-01 00:00:00</StartDate>
</Row>
<Row Index="1">
<Title>Write jQuery example</Title>
<PercentComplete>0.05</PercentComplete>
<DueDate>2013-06-30 00:00:00</DueDate>
<StartDate>2013-01-02 00:00:00</StartDate>
</Row>
</Content>
</List>
When serializing these to JSON using
JsonConvert.SerializeXmlNode(xmldoc, Formatting.Indented);
The first xml becomes this
{
"List": {
"Content": {
"Row": {
"@Index": "0",
"Title": "Testing",
"PercentComplete": "0",
"DueDate": null,
"StartDate": null
}
}
}
}
And the second this
{
"List": {
"Content": {
"Row": [{
"@Index": "0",
"Title": "Update Documentation",
"PercentComplete": "0.5",
"DueDate": "2013-01-31 00:00:00",
"StartDate": "2013-01-01 00:00:00"
}, {
"@Index": "1",
"Title": "Write jQuery example",
"PercentComplete": "0.05",
"DueDate": "2013-06-30 00:00:00",
"StartDate": "2013-01-02 00:00:00"
}]
}
}
}
As clearly can be seen the Row on the second one is an array as should be but not on the first one. Is there any known workaround on this kind of issues or do I need to implement the check in my frontend receiving the JSON (that would be a bit problematic since the structures are very dynamic). The best way would be if there where any way to enforce json.net to always return arrays.
回答1:
From Json.NET documentation: http://james.newtonking.com/projects/json/help/?topic=html/ConvertingJSONandXML.htm
You can force a node to be rendered as an Array by adding the attribute json:Array='true'
to the XML node you are converting to JSON. Also, you need to declare the json prefix namespace at the XML header xmlns:json='http://james.newtonking.com/projects/json'
or else you will get an XML error stating that the json prefix is not declared.
The next example is provided by the documentation:
xml = @"<person xmlns:json='http://james.newtonking.com/projects/json' id='1'>
<name>Alan</name>
<url>http://www.google.com</url>
<role json:Array='true'>Admin</role>
</person>";
Generated output:
{
"person": {
"@id": "1",
"name": "Alan",
"url": "http://www.google.com",
"role": [
"Admin"
]
}
}
回答2:
I did fix this behavior like this
// Handle JsonConvert array bug
var rows = doc.SelectNodes("//Row");
if(rows.Count == 1)
{
var contentNode = doc.SelectSingleNode("//List/Content");
contentNode.AppendChild(doc.CreateNode("element", "Row", ""));
// Convert to JSON and replace the empty element we created but keep the array declaration
returnJson = JsonConvert.SerializeXmlNode(doc).Replace(",null]", "]");
}
else
{
// Convert to JSON
returnJson = JsonConvert.SerializeXmlNode(doc);
}
It's a bit dirty but it works. I'm still interested in other solutions!
回答3:
Giving my +1 to Iván Pérez Gómez and providing some code here to support his answer:
Add the required json.net namespace to the root node:
private static void AddJsonNetRootAttribute(XmlDocument xmlD)
{
XmlAttribute jsonNS = xmlD.CreateAttribute("xmlns", "json", "http://www.w3.org/2000/xmlns/");
jsonNS.Value = "http://james.newtonking.com/projects/json";
xmlD.DocumentElement.SetAttributeNode(jsonNS);
}
And to add json:Array attribute to elements found by xpath:
private static void AddJsonArrayAttributesForXPath(string xpath, XmlDocument doc)
{
var elements = doc.SelectNodes(xpath);
foreach (var element in elements)
{
var el = element as XmlElement;
if (el != null)
{
var jsonArray = doc.CreateAttribute("json", "Array", "http://james.newtonking.com/projects/json");
jsonArray.Value = "true";
el.SetAttributeNode(jsonArray);
}
}
}
Here is a sample of a single child node as a json array:

回答4:
My solution: if JsonConvert doesn't work don't use it. Parse XML into dictionaries/collections and then into Json. At least this way you don't have to hard code any of the element names.
private JsonResult AsJsonResult(XmlDocument result)
{
var kvp = new KeyValuePair<string, object>(result.DocumentElement.Name, Value(result.DocumentElement));
return Json(kvp
, JsonRequestBehavior.AllowGet);
}
/// <summary>
/// Deserializing straight from Xml produces Ugly Json, convert to Dictionaries first to strip out unwanted nesting
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private object Value(XmlNode node)
{
dynamic value;
//If we hit a complex element
if (node.HasChildNodes && !(node.FirstChild is XmlText))
{
//If we hit a collection, it will have children which are also not just text!
if (node.FirstChild.HasChildNodes && !(node.FirstChild.FirstChild is XmlText))
{
//want to return a list of Dictionarys for the children's nodes
//Eat one level of the hierachy and return child nodes as an array
value = new List<object>();
foreach (XmlNode childNode in node.ChildNodes)
{
value.Add(Value(childNode));
}
}
else //regular complex element return childNodes as a dictionary
{
value = new Dictionary<string, object>();
foreach (XmlNode childNode in node.ChildNodes)
{
value.Add(childNode.Name, Value(childNode));
}
}
}
else //Simple element
{
value = node.FirstChild.InnerText;
}
return value;
}
回答5:
Found same problem using XDocument
if (XDocument.Parse("5.0021.0045.00").Descendants("row").Count() > 1) { }
if (XDocument.Parse("<RUT3><row><FromKG>1.00</FromKG><ToKG>5.00</ToKG><Rate>45.00</Rate></row><row><FromKG>6.00</FromKG><ToKG>10.00</ToKG><Rate>65.00</Rate></row><row><FromKG>11.00</FromKG><ToKG>100.00</ToKG><Rate>98.00</Rate></row></RUT3>").Descendants("row").Count() > 1)
{
}
来源:https://stackoverflow.com/questions/14488662/json-net-xml-serialization-misunderstands-arrays