-Optimized IXmlNode memory usage when no child nodes or attributes

This commit is contained in:
James Newton-King
2017-01-15 18:37:06 +13:00
parent b3e2d61708
commit 473baa639b
2 changed files with 164 additions and 47 deletions
@@ -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()
{
@@ -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<IXmlNode>(_node.ChildNodes.Count);
foreach (XmlNode childNode in _node.ChildNodes)
if (!_node.HasChildNodes)
{
_childNodes.Add(WrapNode(childNode));
_childNodes = XmlNodeConverter.EmptyChildNodes;
}
else
{
_childNodes = new List<IXmlNode>(_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<IXmlNode>(_node.Attributes.Count);
foreach (XmlAttribute attribute in _node.Attributes)
if (!HasAttributes)
{
_attributes.Add(WrapNode(attribute));
_attributes = XmlNodeConverter.EmptyChildNodes;
}
else
{
_attributes = new List<IXmlNode>(_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<IXmlNode> 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<IXmlNode>();
foreach (XNode node in Container.Nodes())
if (!HasChildNodes)
{
_childNodes.Add(WrapNode(node));
_childNodes = XmlNodeConverter.EmptyChildNodes;
}
else
{
_childNodes = new List<IXmlNode>();
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<IXmlNode> EmptyChildNodes = new List<IXmlNode>();
private readonly XObject _xmlObject;
public XObjectWrapper(XObject xmlObject)
@@ -814,12 +870,12 @@ namespace Newtonsoft.Json.Converters
public virtual List<IXmlNode> ChildNodes
{
get { return EmptyChildNodes; }
get { return XmlNodeConverter.EmptyChildNodes; }
}
public virtual List<IXmlNode> 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<IXmlNode>();
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<IXmlNode>();
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
/// </summary>
public class XmlNodeConverter : JsonConverter
{
internal static readonly List<IXmlNode> EmptyChildNodes = new List<IXmlNode>();
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;