JSON.Net Xml Сериализация неправильно понимает массивы
У меня есть автогенерируемые xmls, где некоторые части xml могут иметь несколько строк, а некоторые - нет. В результате, если есть одна строка, возвращается один json node, и если у меня есть несколько строк, возвращается массив с json-узлами.
xmls может выглядеть так:
<List>
<Content>
<Row Index="0">
<Title>Testing</Title>
<PercentComplete>0</PercentComplete>
<DueDate/>
<StartDate/>
</Row>
</Content>
</List>
Или с несколькими строками
<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>
При сериализации их в JSON с помощью
JsonConvert.SerializeXmlNode(xmldoc, Formatting.Indented);
Первый xml становится этим
{
"List": {
"Content": {
"Row": {
"@Index": "0",
"Title": "Testing",
"PercentComplete": "0",
"DueDate": null,
"StartDate": null
}
}
}
}
И второе это
{
"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"
}]
}
}
}
Как ясно видно, строка на втором является массивом, который должен быть, но не первым. Существует ли какое-либо известное обходное решение для такого рода проблем или мне нужно выполнить проверку в моем интерфейсе, получая JSON (это было бы немного проблематично, поскольку структуры очень динамичны). Лучше всего было бы, если бы там, где любой способ принудить json.net всегда возвращать массивы.
Ответы
Ответ 1
Я исправил это поведение следующим образом
// 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);
}
Это немного грязно, но это работает. Меня все еще интересуют другие решения!
Ответ 2
Из документации Json.NET:
http://james.newtonking.com/projects/json/help/?topic=html/ConvertingJSONandXML.htm
Вы можете принудительно отобразить node как массив, добавив атрибут json:Array='true'
в XML node, который вы конвертируете в JSON. Кроме того, вам нужно объявить пространство имен префиксов json в заголовке XML xmlns:json='http://james.newtonking.com/projects/json'
, иначе вы получите сообщение об ошибке XML, в котором указано, что префикс json не объявлен.
Следующий пример представлен документацией:
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>";
Сгенерированный вывод:
{
"person": {
"@id": "1",
"name": "Alan",
"url": "http://www.google.com",
"role": [
"Admin"
]
}
}
Ответ 3
Предоставление моего +1 Ивану Пересу Гомесу и предоставление некоторого кода для поддержки его ответа:
Добавьте нужное пространство имен json.net в корневой каталог 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);
}
И добавить атрибут json: Array к элементам, найденным по пути 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);
}
}
}
Вот пример одного дочернего node как массива json:
![Here is a sample of a single child node as a json array:]()
Ответ 4
Мое решение: если JsonConvert не работает, не используйте его. Разбирайте XML в словари/коллекции, а затем в Json. По крайней мере, таким образом вам не нужно жестко кодировать любые имена элементов.
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 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;
}