From 473baa639b6c36f7b1e53faaab69577434a86872 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 15 Jan 2017 18:37:06 +1300 Subject: [PATCH] -Optimized IXmlNode memory usage when no child nodes or attributes --- .../Converters/XmlNodeConverterTest.cs | 41 +++++ .../Converters/XmlNodeConverter.cs | 170 +++++++++++++----- 2 files changed, 164 insertions(+), 47 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs index 7fabdb25..ed8fe976 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs @@ -2745,6 +2745,47 @@ namespace Newtonsoft.Json.Tests.Converters #endif #if !NET20 + [Test] + public void Serialize_XDocument_NoRoot() + { + XDocument d = new XDocument(); + + string json = JsonConvert.SerializeXNode(d); + + Assert.AreEqual(@"{}", json); + } + + [Test] + public void Deserialize_XDocument_NoRoot() + { + XDocument d = JsonConvert.DeserializeXNode(@"{}"); + + Assert.AreEqual(null, d.Root); + Assert.AreEqual(null, d.Declaration); + } + + [Test] + public void Serialize_XDocument_NoRootWithDeclaration() + { + XDocument d = new XDocument(); + d.Declaration = new XDeclaration("Version!", "Encoding!", "Standalone!"); + + string json = JsonConvert.SerializeXNode(d); + + Assert.AreEqual(@"{""?xml"":{""@version"":""Version!"",""@encoding"":""Encoding!"",""@standalone"":""Standalone!""}}", json); + } + + [Test] + public void Deserialize_XDocument_NoRootWithDeclaration() + { + XDocument d = JsonConvert.DeserializeXNode(@"{""?xml"":{""@version"":""Version!"",""@encoding"":""Encoding!"",""@standalone"":""Standalone!""}}"); + + Assert.AreEqual(null, d.Root); + Assert.AreEqual("Version!", d.Declaration.Version); + Assert.AreEqual("Encoding!", d.Declaration.Encoding); + Assert.AreEqual("Standalone!", d.Declaration.Standalone); + } + [Test] public void DateTimeToXml_Unspecified() { diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs index 110dec53..7e82adb5 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -258,10 +258,17 @@ namespace Newtonsoft.Json.Converters // cache results to prevent multiple reads which kills perf in large documents if (_childNodes == null) { - _childNodes = new List(_node.ChildNodes.Count); - foreach (XmlNode childNode in _node.ChildNodes) + if (!_node.HasChildNodes) { - _childNodes.Add(WrapNode(childNode)); + _childNodes = XmlNodeConverter.EmptyChildNodes; + } + else + { + _childNodes = new List(_node.ChildNodes.Count); + foreach (XmlNode childNode in _node.ChildNodes) + { + _childNodes.Add(WrapNode(childNode)); + } } } @@ -269,6 +276,11 @@ namespace Newtonsoft.Json.Converters } } + protected virtual bool HasChildNodes + { + get { return _node.HasChildNodes; } + } + internal static IXmlNode WrapNode(XmlNode node) { switch (node.NodeType) @@ -288,19 +300,21 @@ namespace Newtonsoft.Json.Converters { get { - if (_node.Attributes == null) - { - return null; - } - // attributes is read multiple times // cache results to prevent multiple reads which kills perf in large documents if (_attributes == null) { - _attributes = new List(_node.Attributes.Count); - foreach (XmlAttribute attribute in _node.Attributes) + if (!HasAttributes) { - _attributes.Add(WrapNode(attribute)); + _attributes = XmlNodeConverter.EmptyChildNodes; + } + else + { + _attributes = new List(_node.Attributes.Count); + foreach (XmlAttribute attribute in _node.Attributes) + { + _attributes.Add(WrapNode(attribute)); + } } } @@ -308,6 +322,20 @@ namespace Newtonsoft.Json.Converters } } + private bool HasAttributes + { + get + { + XmlElement element = _node as XmlElement; + if (element != null) + { + return element.HasAttributes; + } + + return _node.Attributes?.Count > 0; + } + } + public IXmlNode ParentNode { get @@ -491,8 +519,7 @@ namespace Newtonsoft.Json.Converters get { List childNodes = base.ChildNodes; - - if (Document.Declaration != null && childNodes[0].NodeType != XmlNodeType.XmlDeclaration) + if (Document.Declaration != null && (childNodes.Count == 0 || childNodes[0].NodeType != XmlNodeType.XmlDeclaration)) { childNodes.Insert(0, new XDeclarationWrapper(Document.Declaration)); } @@ -501,6 +528,19 @@ namespace Newtonsoft.Json.Converters } } + protected override bool HasChildNodes + { + get + { + if (base.HasChildNodes) + { + return true; + } + + return Document.Declaration != null; + } + } + public IXmlNode CreateComment(string text) { return new XObjectWrapper(new XComment(text)); @@ -701,10 +741,17 @@ namespace Newtonsoft.Json.Converters // cache results to prevent multiple reads which kills perf in large documents if (_childNodes == null) { - _childNodes = new List(); - foreach (XNode node in Container.Nodes()) + if (!HasChildNodes) { - _childNodes.Add(WrapNode(node)); + _childNodes = XmlNodeConverter.EmptyChildNodes; + } + else + { + _childNodes = new List(); + foreach (XNode node in Container.Nodes()) + { + _childNodes.Add(WrapNode(node)); + } } } @@ -712,6 +759,16 @@ namespace Newtonsoft.Json.Converters } } + protected virtual bool HasChildNodes + { + get + { + // use last node for performance + // container linked list starts with lastnode + return Container.LastNode != null; + } + } + public override IXmlNode ParentNode { get @@ -789,7 +846,6 @@ namespace Newtonsoft.Json.Converters internal class XObjectWrapper : IXmlNode { - private static readonly List EmptyChildNodes = new List(); private readonly XObject _xmlObject; public XObjectWrapper(XObject xmlObject) @@ -814,12 +870,12 @@ namespace Newtonsoft.Json.Converters public virtual List ChildNodes { - get { return EmptyChildNodes; } + get { return XmlNodeConverter.EmptyChildNodes; } } public virtual List Attributes { - get { return null; } + get { return XmlNodeConverter.EmptyChildNodes; } } public virtual IXmlNode ParentNode @@ -915,32 +971,24 @@ namespace Newtonsoft.Json.Converters // cache results to prevent multiple reads which kills perf in large documents if (_attributes == null) { - _attributes = new List(); - foreach (XAttribute attribute in Element.Attributes()) + if (!Element.HasAttributes && !HasImplicitNamespaceAttribute(NamespaceUri)) { - _attributes.Add(new XAttributeWrapper(attribute)); + _attributes = XmlNodeConverter.EmptyChildNodes; } - - // ensure elements created with a namespace but no namespace attribute are converted correctly - // e.g. new XElement("{http://example.com}MyElement"); - string namespaceUri = NamespaceUri; - if (!string.IsNullOrEmpty(namespaceUri) && namespaceUri != ParentNode?.NamespaceUri) + else { - if (string.IsNullOrEmpty(GetPrefixOfNamespace(namespaceUri))) + _attributes = new List(); + foreach (XAttribute attribute in Element.Attributes()) { - bool namespaceDeclared = false; - foreach (IXmlNode attribute in _attributes) - { - if (attribute.LocalName == "xmlns" && string.IsNullOrEmpty(attribute.NamespaceUri) && attribute.Value == namespaceUri) - { - namespaceDeclared = true; - } - } + _attributes.Add(new XAttributeWrapper(attribute)); + } - if (!namespaceDeclared) - { - _attributes.Insert(0, new XAttributeWrapper(new XAttribute("xmlns", namespaceUri))); - } + // ensure elements created with a namespace but no namespace attribute are converted correctly + // e.g. new XElement("{http://example.com}MyElement"); + string namespaceUri = NamespaceUri; + if (HasImplicitNamespaceAttribute(namespaceUri)) + { + _attributes.Insert(0, new XAttributeWrapper(new XAttribute("xmlns", namespaceUri))); } } } @@ -949,6 +997,35 @@ namespace Newtonsoft.Json.Converters } } + private bool HasImplicitNamespaceAttribute(string namespaceUri) + { + if (!string.IsNullOrEmpty(namespaceUri) && namespaceUri != ParentNode?.NamespaceUri) + { + if (string.IsNullOrEmpty(GetPrefixOfNamespace(namespaceUri))) + { + bool namespaceDeclared = false; + + if (Element.HasAttributes) + { + foreach (XAttribute attribute in Element.Attributes()) + { + if (attribute.Name.LocalName == "xmlns" && string.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Value == namespaceUri) + { + namespaceDeclared = true; + } + } + } + + if (!namespaceDeclared) + { + return true; + } + } + } + + return false; + } + public override IXmlNode AppendChild(IXmlNode newChild) { IXmlNode result = base.AppendChild(newChild); @@ -990,6 +1067,8 @@ namespace Newtonsoft.Json.Converters /// public class XmlNodeConverter : JsonConverter { + internal static readonly List EmptyChildNodes = new List(); + private const string TextName = "#text"; private const string CommentName = "#comment"; private const string CDataName = "#cdata-section"; @@ -1161,14 +1240,11 @@ namespace Newtonsoft.Json.Converters private bool IsArray(IXmlNode node) { - if (node.Attributes != null) + foreach (IXmlNode attribute in node.Attributes) { - foreach (IXmlNode attribute in node.Attributes) + if (attribute.LocalName == "Array" && attribute.NamespaceUri == JsonNamespaceUri) { - if (attribute.LocalName == "Array" && attribute.NamespaceUri == JsonNamespaceUri) - { - return XmlConvert.ToBoolean(attribute.Value); - } + return XmlConvert.ToBoolean(attribute.Value); } } @@ -1282,7 +1358,7 @@ namespace Newtonsoft.Json.Converters // write elements with a single text child as a name value pair writer.WriteValue(node.ChildNodes[0].Value); } - else if (node.ChildNodes.Count == 0 && CollectionUtils.IsNullOrEmpty(node.Attributes)) + else if (node.ChildNodes.Count == 0 && node.Attributes.Count == 0) { IXmlElement element = (IXmlElement)node;