<PackageReference Include="Newtonsoft.Json" Version="6.0.1" />

XmlNodeConverter

Converts XML to and from JSON.
using Newtonsoft.Json.Utilities; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml; using System.Xml.Linq; namespace Newtonsoft.Json.Converters { public class XmlNodeConverter : JsonConverter { private const string TextName = "#text"; private const string CommentName = "#comment"; private const string CDataName = "#cdata-section"; private const string WhitespaceName = "#whitespace"; private const string SignificantWhitespaceName = "#significant-whitespace"; private const string DeclarationName = "?xml"; private const string JsonNamespaceUri = "http://james.newtonking.com/projects/json"; public string DeserializeRootElementName { get; set; } public bool WriteArrayAttribute { get; set; } public bool OmitRootObject { get; set; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { IXmlNode node = WrapXml(value); XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable()); PushParentNamespaces(node, manager); if (!OmitRootObject) writer.WriteStartObject(); SerializeNode(writer, node, manager, !OmitRootObject); if (!OmitRootObject) writer.WriteEndObject(); } private IXmlNode WrapXml(object value) { if (value is XObject) return XContainerWrapper.WrapNode((XObject)value); if (value is XmlNode) return new XmlNodeWrapper((XmlNode)value); throw new ArgumentException("Value must be an XML object.", "value"); } private void PushParentNamespaces(IXmlNode node, XmlNamespaceManager manager) { List<IXmlNode> list = null; IXmlNode xmlNode = node; while ((xmlNode = xmlNode.ParentNode) != null) { if (xmlNode.NodeType == XmlNodeType.Element) { if (list == null) list = new List<IXmlNode>(); list.Add(xmlNode); } } if (list != null) { list.Reverse(); foreach (IXmlNode item in list) { manager.PushScope(); foreach (IXmlNode attribute in item.Attributes) { if (attribute.NamespaceUri == "http://www.w3.org/2000/xmlns/" && attribute.LocalName != "xmlns") manager.AddNamespace(attribute.LocalName, attribute.Value); } } } } private string ResolveFullName(IXmlNode node, XmlNamespaceManager manager) { string text = (node.NamespaceUri == null || (node.LocalName == "xmlns" && node.NamespaceUri == "http://www.w3.org/2000/xmlns/")) ? null : manager.LookupPrefix(node.NamespaceUri); if (!string.IsNullOrEmpty(text)) return text + ":" + node.LocalName; return node.LocalName; } private string GetPropertyName(IXmlNode node, XmlNamespaceManager manager) { switch (node.NodeType) { case XmlNodeType.Attribute: if (node.NamespaceUri == "http://james.newtonking.com/projects/json") return "$" + node.LocalName; return "@" + ResolveFullName(node, manager); case XmlNodeType.CDATA: return "#cdata-section"; case XmlNodeType.Comment: return "#comment"; case XmlNodeType.Element: return ResolveFullName(node, manager); case XmlNodeType.ProcessingInstruction: return "?" + ResolveFullName(node, manager); case XmlNodeType.XmlDeclaration: return "?xml"; case XmlNodeType.SignificantWhitespace: return "#significant-whitespace"; case XmlNodeType.Text: return "#text"; case XmlNodeType.Whitespace: return "#whitespace"; default: throw new JsonSerializationException("Unexpected XmlNodeType when getting node name: " + node.NodeType); } } private bool IsArray(IXmlNode node) { IXmlNode xmlNode = (node.Attributes != null) ? node.Attributes.SingleOrDefault(delegate(IXmlNode a) { if (a.LocalName == "Array") return a.NamespaceUri == "http://james.newtonking.com/projects/json"; return false; }) : null; if (xmlNode != null) return XmlConvert.ToBoolean(xmlNode.Value); return false; } private void SerializeGroupedNodes(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName) { Dictionary<string, List<IXmlNode>> dictionary = new Dictionary<string, List<IXmlNode>>(); for (int i = 0; i < node.ChildNodes.Count; i++) { IXmlNode xmlNode = node.ChildNodes[i]; string propertyName = GetPropertyName(xmlNode, manager); if (!dictionary.TryGetValue(propertyName, out List<IXmlNode> value)) { value = new List<IXmlNode>(); dictionary.Add(propertyName, value); } value.Add(xmlNode); } foreach (KeyValuePair<string, List<IXmlNode>> item in dictionary) { List<IXmlNode> value2 = item.Value; if (value2.Count == 1 && !IsArray(value2[0])) SerializeNode(writer, value2[0], manager, writePropertyName); else { string key = item.Key; if (writePropertyName) writer.WritePropertyName(key); writer.WriteStartArray(); for (int j = 0; j < value2.Count; j++) { SerializeNode(writer, value2[j], manager, false); } writer.WriteEndArray(); } } } private void SerializeNode(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName) { switch (node.NodeType) { case XmlNodeType.Document: case XmlNodeType.DocumentFragment: SerializeGroupedNodes(writer, node, manager, writePropertyName); break; case XmlNodeType.Element: if (IsArray(node) && node.ChildNodes.All((IXmlNode n) => n.LocalName == node.LocalName) && node.ChildNodes.Count > 0) SerializeGroupedNodes(writer, node, manager, false); else { manager.PushScope(); foreach (IXmlNode attribute in node.Attributes) { if (attribute.NamespaceUri == "http://www.w3.org/2000/xmlns/") { string prefix = (attribute.LocalName != "xmlns") ? attribute.LocalName : string.Empty; string value = attribute.Value; manager.AddNamespace(prefix, value); } } if (writePropertyName) writer.WritePropertyName(GetPropertyName(node, manager)); if (!ValueAttributes(node.Attributes).Any() && node.ChildNodes.Count == 1 && node.ChildNodes[0].NodeType == XmlNodeType.Text) writer.WriteValue(node.ChildNodes[0].Value); else if (node.ChildNodes.Count == 0 && CollectionUtils.IsNullOrEmpty(node.Attributes)) { writer.WriteNull(); } else { writer.WriteStartObject(); for (int i = 0; i < node.Attributes.Count; i++) { SerializeNode(writer, node.Attributes[i], manager, true); } SerializeGroupedNodes(writer, node, manager, true); writer.WriteEndObject(); } manager.PopScope(); } break; case XmlNodeType.Comment: if (writePropertyName) writer.WriteComment(node.Value); break; case XmlNodeType.Attribute: case XmlNodeType.Text: case XmlNodeType.CDATA: case XmlNodeType.ProcessingInstruction: case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: if ((!(node.NamespaceUri == "http://www.w3.org/2000/xmlns/") || !(node.Value == "http://james.newtonking.com/projects/json")) && (!(node.NamespaceUri == "http://james.newtonking.com/projects/json") || !(node.LocalName == "Array"))) { if (writePropertyName) writer.WritePropertyName(GetPropertyName(node, manager)); writer.WriteValue(node.Value); } break; case XmlNodeType.XmlDeclaration: { IXmlDeclaration xmlDeclaration = (IXmlDeclaration)node; writer.WritePropertyName(GetPropertyName(node, manager)); writer.WriteStartObject(); if (!string.IsNullOrEmpty(xmlDeclaration.Version)) { writer.WritePropertyName("@version"); writer.WriteValue(xmlDeclaration.Version); } if (!string.IsNullOrEmpty(xmlDeclaration.Encoding)) { writer.WritePropertyName("@encoding"); writer.WriteValue(xmlDeclaration.Encoding); } if (!string.IsNullOrEmpty(xmlDeclaration.Standalone)) { writer.WritePropertyName("@standalone"); writer.WriteValue(xmlDeclaration.Standalone); } writer.WriteEndObject(); break; } default: throw new JsonSerializationException("Unexpected XmlNodeType when serializing nodes: " + node.NodeType); } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable()); IXmlDocument xmlDocument = null; IXmlNode xmlNode = null; if (typeof(XObject).IsAssignableFrom(objectType)) { if (objectType != typeof(XDocument) && objectType != typeof(XElement)) throw new JsonSerializationException("XmlNodeConverter only supports deserializing XDocument or XElement."); XDocument document = new XDocument(); xmlDocument = new XDocumentWrapper(document); xmlNode = xmlDocument; } if (typeof(XmlNode).IsAssignableFrom(objectType)) { if (objectType != typeof(XmlDocument)) throw new JsonSerializationException("XmlNodeConverter only supports deserializing XmlDocuments"); XmlDocument document2 = new XmlDocument(); xmlDocument = new XmlDocumentWrapper(document2); xmlNode = xmlDocument; } if (xmlDocument == null || xmlNode == null) throw new JsonSerializationException("Unexpected type when converting XML: " + objectType); if (reader.TokenType != JsonToken.StartObject) throw new JsonSerializationException("XmlNodeConverter can only convert JSON that begins with an object."); if (!string.IsNullOrEmpty(DeserializeRootElementName)) ReadElement(reader, xmlDocument, xmlNode, DeserializeRootElementName, manager); else { reader.Read(); DeserializeNode(reader, xmlDocument, manager, xmlNode); } if (objectType == typeof(XElement)) { XElement xElement = (XElement)xmlDocument.DocumentElement.WrappedNode; xElement.Remove(); return xElement; } return xmlDocument.WrappedNode; } private void DeserializeValue(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, string propertyName, IXmlNode currentNode) { switch (propertyName) { case "#text": currentNode.AppendChild(document.CreateTextNode(reader.Value.ToString())); break; case "#cdata-section": currentNode.AppendChild(document.CreateCDataSection(reader.Value.ToString())); break; case "#whitespace": currentNode.AppendChild(document.CreateWhitespace(reader.Value.ToString())); break; case "#significant-whitespace": currentNode.AppendChild(document.CreateSignificantWhitespace(reader.Value.ToString())); break; default: if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?') CreateInstruction(reader, document, currentNode, propertyName); else if (reader.TokenType == JsonToken.StartArray) { ReadArrayElements(reader, document, propertyName, currentNode, manager); } else { ReadElement(reader, document, currentNode, propertyName, manager); } break; } } private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager) { if (string.IsNullOrEmpty(propertyName)) throw new JsonSerializationException("XmlNodeConverter cannot convert JSON with an empty property name to XML."); Dictionary<string, string> dictionary = ReadAttributeElements(reader, manager); string prefix = MiscellaneousUtils.GetPrefix(propertyName); if (propertyName.StartsWith("@")) { string text = propertyName.Substring(1); string value = reader.Value.ToString(); string prefix2 = MiscellaneousUtils.GetPrefix(text); IXmlNode attributeNode = (!string.IsNullOrEmpty(prefix2)) ? document.CreateAttribute(text, manager.LookupNamespace(prefix2), value) : document.CreateAttribute(text, value); ((IXmlElement)currentNode).SetAttributeNode(attributeNode); } else { IXmlElement xmlElement = CreateElement(propertyName, document, prefix, manager); currentNode.AppendChild(xmlElement); foreach (KeyValuePair<string, string> item in dictionary) { string prefix3 = MiscellaneousUtils.GetPrefix(item.Key); IXmlNode attributeNode2 = (!string.IsNullOrEmpty(prefix3)) ? document.CreateAttribute(item.Key, manager.LookupNamespace(prefix3), item.Value) : document.CreateAttribute(item.Key, item.Value); xmlElement.SetAttributeNode(attributeNode2); } if (reader.TokenType == JsonToken.String || reader.TokenType == JsonToken.Integer || reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Boolean || reader.TokenType == JsonToken.Date) xmlElement.AppendChild(document.CreateTextNode(ConvertTokenToXmlValue(reader))); else if (reader.TokenType != JsonToken.Null && reader.TokenType != JsonToken.EndObject) { manager.PushScope(); DeserializeNode(reader, document, manager, xmlElement); manager.PopScope(); } } } private string ConvertTokenToXmlValue(JsonReader reader) { if (reader.TokenType == JsonToken.String) return reader.Value.ToString(); if (reader.TokenType == JsonToken.Integer) return XmlConvert.ToString(Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture)); if (reader.TokenType == JsonToken.Float) return XmlConvert.ToString(Convert.ToDouble(reader.Value, CultureInfo.InvariantCulture)); if (reader.TokenType == JsonToken.Boolean) return XmlConvert.ToString(Convert.ToBoolean(reader.Value, CultureInfo.InvariantCulture)); if (reader.TokenType == JsonToken.Date) { DateTime value = Convert.ToDateTime(reader.Value, CultureInfo.InvariantCulture); return XmlConvert.ToString(value, DateTimeUtils.ToSerializationMode(value.Kind)); } if (reader.TokenType == JsonToken.Null) return null; throw JsonSerializationException.Create(reader, "Cannot get an XML string value from token type '{0}'.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType)); } private void ReadArrayElements(JsonReader reader, IXmlDocument document, string propertyName, IXmlNode currentNode, XmlNamespaceManager manager) { string prefix = MiscellaneousUtils.GetPrefix(propertyName); IXmlElement xmlElement = CreateElement(propertyName, document, prefix, manager); currentNode.AppendChild(xmlElement); int num = 0; while (reader.Read() && reader.TokenType != JsonToken.EndArray) { DeserializeValue(reader, document, manager, propertyName, xmlElement); num++; } if (WriteArrayAttribute) AddJsonArrayAttribute(xmlElement, document); if (num == 1 && WriteArrayAttribute) { IXmlElement element = xmlElement.ChildNodes.OfType<IXmlElement>().Single((IXmlElement n) => n.LocalName == propertyName); AddJsonArrayAttribute(element, document); } } private void AddJsonArrayAttribute(IXmlElement element, IXmlDocument document) { element.SetAttributeNode(document.CreateAttribute("json:Array", "http://james.newtonking.com/projects/json", "true")); if (element is XElementWrapper && element.GetPrefixOfNamespace("http://james.newtonking.com/projects/json") == null) element.SetAttributeNode(document.CreateAttribute("xmlns:json", "http://www.w3.org/2000/xmlns/", "http://james.newtonking.com/projects/json")); } private Dictionary<string, string> ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); bool flag = false; bool flag2 = false; if (reader.TokenType != JsonToken.String && reader.TokenType != JsonToken.Null && reader.TokenType != JsonToken.Boolean && reader.TokenType != JsonToken.Integer && reader.TokenType != JsonToken.Float && reader.TokenType != JsonToken.Date && reader.TokenType != JsonToken.StartConstructor) { while (!flag && !flag2 && reader.Read()) { switch (reader.TokenType) { case JsonToken.PropertyName: { string text = reader.Value.ToString(); if (!string.IsNullOrEmpty(text)) { switch (text[0]) { case '@': { text = text.Substring(1); reader.Read(); string value = ConvertTokenToXmlValue(reader); dictionary.Add(text, value); if (IsNamespaceAttribute(text, out string prefix)) manager.AddNamespace(prefix, value); break; } case '$': { text = text.Substring(1); reader.Read(); string value = reader.Value.ToString(); string text2 = manager.LookupPrefix("http://james.newtonking.com/projects/json"); if (text2 == null) { int? nullable = null; while (manager.LookupNamespace("json" + nullable) != null) { nullable = nullable.GetValueOrDefault() + 1; } text2 = "json" + nullable; dictionary.Add("xmlns:" + text2, "http://james.newtonking.com/projects/json"); manager.AddNamespace(text2, "http://james.newtonking.com/projects/json"); } dictionary.Add(text2 + ":" + text, value); break; } default: flag = true; break; } } else flag = true; break; } case JsonToken.EndObject: flag2 = true; break; default: throw new JsonSerializationException("Unexpected JsonToken: " + reader.TokenType); } } } return dictionary; } private void CreateInstruction(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName) { if (propertyName == "?xml") { string version = null; string encoding = null; string standalone = null; while (reader.Read() && reader.TokenType != JsonToken.EndObject) { switch (reader.Value.ToString()) { case "@version": reader.Read(); version = reader.Value.ToString(); break; case "@encoding": reader.Read(); encoding = reader.Value.ToString(); break; case "@standalone": reader.Read(); standalone = reader.Value.ToString(); break; default: throw new JsonSerializationException("Unexpected property name encountered while deserializing XmlDeclaration: " + reader.Value); } } IXmlNode newChild = document.CreateXmlDeclaration(version, encoding, standalone); currentNode.AppendChild(newChild); } else { IXmlNode newChild2 = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString()); currentNode.AppendChild(newChild2); } } private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager) { string text = string.IsNullOrEmpty(elementPrefix) ? manager.DefaultNamespace : manager.LookupNamespace(elementPrefix); return (!string.IsNullOrEmpty(text)) ? document.CreateElement(elementName, text) : document.CreateElement(elementName); } private void DeserializeNode(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, IXmlNode currentNode) { do { switch (reader.TokenType) { case JsonToken.EndObject: case JsonToken.EndArray: return; case JsonToken.PropertyName: { if (currentNode.NodeType == XmlNodeType.Document && document.DocumentElement != null) throw new JsonSerializationException("JSON root object has multiple properties. The root object must have a single property in order to create a valid XML document. Consider specifing a DeserializeRootElementName."); string propertyName = reader.Value.ToString(); reader.Read(); if (reader.TokenType == JsonToken.StartArray) { int num = 0; while (reader.Read() && reader.TokenType != JsonToken.EndArray) { DeserializeValue(reader, document, manager, propertyName, currentNode); num++; } if (num == 1 && WriteArrayAttribute) { IXmlElement element = currentNode.ChildNodes.OfType<IXmlElement>().Single((IXmlElement n) => n.LocalName == propertyName); AddJsonArrayAttribute(element, document); } } else DeserializeValue(reader, document, manager, propertyName, currentNode); break; } case JsonToken.StartConstructor: { string propertyName2 = reader.Value.ToString(); while (reader.Read() && reader.TokenType != JsonToken.EndConstructor) { DeserializeValue(reader, document, manager, propertyName2, currentNode); } break; } case JsonToken.Comment: currentNode.AppendChild(document.CreateComment((string)reader.Value)); break; default: throw new JsonSerializationException("Unexpected JsonToken when deserializing node: " + reader.TokenType); } } while (reader.TokenType == JsonToken.PropertyName || reader.Read()); } private bool IsNamespaceAttribute(string attributeName, out string prefix) { if (attributeName.StartsWith("xmlns", StringComparison.Ordinal)) { if (attributeName.Length == 5) { prefix = string.Empty; return true; } if (attributeName[5] == ':') { prefix = attributeName.Substring(6, attributeName.Length - 6); return true; } } prefix = null; return false; } private IEnumerable<IXmlNode> ValueAttributes(IEnumerable<IXmlNode> c) { return from a in c where a.NamespaceUri != "http://james.newtonking.com/projects/json" select a; } public override bool CanConvert(Type valueType) { if (typeof(XObject).IsAssignableFrom(valueType)) return true; if (typeof(XmlNode).IsAssignableFrom(valueType)) return true; return false; } } }