From 55c87a0e7354a369422b11b5bb2bfd885b13a237 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 10 Apr 2016 11:36:21 +1200 Subject: [PATCH] -Fixed converting XElements without namespace attribute to JSON --- .../Converters/XmlNodeConverterTest.cs | 26 +++++++++ .../Serialization/ExtensionDataTests.cs | 34 +++++++++++ .../MetadataPropertyHandlingTests.cs | 58 +++++++++++++++++++ .../Converters/XmlNodeConverter.cs | 57 +++++++++++++++++- 4 files changed, 172 insertions(+), 3 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs index 4c1c2c7b..59538f5c 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs @@ -2670,6 +2670,32 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual("null", json); } + + [Test] + public void SerializeExplicitAttributeNamespace() + { + var original = XElement.Parse(""); + Assert.AreEqual(@"", original.ToString()); + + var json = JsonConvert.SerializeObject(original); + Assert.AreEqual(@"{""MyElement"":{""@xmlns"":""http://example.com""}}", json); + + var deserialized = JsonConvert.DeserializeObject(json); + Assert.AreEqual(@"", deserialized.ToString()); + } + + [Test] + public void SerializeImplicitAttributeNamespace() + { + var original = new XElement("{http://example.com}MyElement"); + Assert.AreEqual(@"", original.ToString()); + + var json = JsonConvert.SerializeObject(original); + Assert.AreEqual(@"{""MyElement"":{""@xmlns"":""http://example.com""}}", json); + + var deserialized = JsonConvert.DeserializeObject(json); + Assert.AreEqual(@"", deserialized.ToString()); + } #endif } } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs index 6acc24df..0a366552 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs @@ -374,6 +374,40 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(7, c.ExtensionData.Count); } + [JsonObject(MemberSerialization.OptIn)] + public class MyClass + { + public int NotForJson { get; set; } + + [JsonPropertyAttribute(Required = Required.Always)] + public int ForJson { get; set; } + + [JsonExtensionData(ReadData = true, WriteData = true)] + public IDictionary ExtraInfoJson { get; set; } + + public MyClass(MyClass other = null) + { + if (other != null) + { + // copy construct + } + } + } + + [Test] + public void PopulateWithExtensionData() + { + string jsonStirng = @"{ ""ForJson"" : 33 , ""extra1"" : 11, ""extra2"" : 22 }"; + + MyClass c = new MyClass(); + + JsonConvert.PopulateObject(jsonStirng, c); + + Assert.AreEqual(2, c.ExtraInfoJson.Count); + Assert.AreEqual(11, (int)c.ExtraInfoJson["extra1"]); + Assert.AreEqual(22, (int)c.ExtraInfoJson["extra2"]); + } + public class MultipleExtensionDataAttributesTestClass { public string Name { get; set; } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs index f413e85f..44d195cc 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs @@ -528,6 +528,64 @@ namespace Newtonsoft.Json.Tests.Serialization JObject o = (JObject)actual.Payload; Assert.AreEqual("System.Byte[], mscorlib", (string)o["$type"]); Assert.AreEqual("AAECAwQFBgcICQ==", (string)o["$value"]); + Assert.AreEqual(null, o.Parent); + } + + [Test] + public void ReadAhead_JObject_NoParent() + { + ItemWithUntypedPayload actual = JsonConvert.DeserializeObject(@"{ + ""Payload"": {} +}", + new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead + }); + + JObject o = (JObject)actual.Payload; + Assert.AreEqual(null, o.Parent); + } + + public class ItemWithJTokens + { + public JValue Payload1 { get; set; } + public JObject Payload2 { get; set; } + public JArray Payload3 { get; set; } + } + + [Test] + public void ReadAhead_TypedJValue_NoParent() + { + ItemWithJTokens actual = (ItemWithJTokens)JsonConvert.DeserializeObject(@"{ + ""Payload1"": 1, + ""Payload2"": {}, + ""Payload3"": [1], + ""$type"": ""Newtonsoft.Json.Tests.Serialization.MetadataPropertyHandlingTests+ItemWithJTokens, Newtonsoft.Json.Tests"" +}", + new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead, + TypeNameHandling = TypeNameHandling.All + }); + + Assert.AreEqual(null, actual.Payload1.Parent); + Assert.AreEqual(null, actual.Payload2.Parent); + Assert.AreEqual(null, actual.Payload3.Parent); + } + + [Test] + public void ReadAhead_JArray_NoParent() + { + ItemWithUntypedPayload actual = JsonConvert.DeserializeObject(@"{ + ""Payload"": [1] +}", + new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead + }); + + JArray o = (JArray)actual.Payload; + Assert.AreEqual(null, o.Parent); } public class ItemWithUntypedPayload diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs index 051f6768..874c508b 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -46,7 +46,6 @@ using System.Linq; namespace Newtonsoft.Json.Converters { - #region XmlNodeWrappers #if !DOTNET && !PORTABLE && !PORTABLE40 internal class XmlDocumentWrapper : XmlNodeWrapper, IXmlDocument @@ -235,6 +234,7 @@ namespace Newtonsoft.Json.Converters { private readonly XmlNode _node; private IList _childNodes; + private IList _attributes; public XmlNodeWrapper(XmlNode node) { @@ -295,7 +295,14 @@ namespace Newtonsoft.Json.Converters return null; } - return _node.Attributes.Cast().Select(WrapNode).ToList(); + // attributes is read multiple times + // cache results to prevent multiple reads which kills perf in large documents + if (_attributes == null) + { + _attributes = _node.Attributes.Cast().Select(WrapNode).ToList(); + } + + return _attributes; } } @@ -327,6 +334,7 @@ namespace Newtonsoft.Json.Converters XmlNodeWrapper xmlNodeWrapper = (XmlNodeWrapper)newChild; _node.AppendChild(xmlNodeWrapper._node); _childNodes = null; + _attributes = null; return newChild; } @@ -861,6 +869,8 @@ namespace Newtonsoft.Json.Converters internal class XElementWrapper : XContainerWrapper, IXmlElement { + private IList _attributes; + private XElement Element { get { return (XElement)WrappedNode; } @@ -875,11 +885,52 @@ namespace Newtonsoft.Json.Converters { XObjectWrapper wrapper = (XObjectWrapper)attribute; Element.Add(wrapper.WrappedNode); + _attributes = null; } public override IList Attributes { - get { return Element.Attributes().Select(a => new XAttributeWrapper(a)).Cast().ToList(); } + get + { + // attributes is read multiple times + // cache results to prevent multiple reads which kills perf in large documents + if (_attributes == null) + { + _attributes = Element.Attributes().Select(a => new XAttributeWrapper(a)).Cast().ToList(); + + // 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) + { + if (string.IsNullOrEmpty(GetPrefixOfNamespace(namespaceUri))) + { + bool namespaceDeclared = false; + foreach (IXmlNode attribute in _attributes) + { + if (attribute.LocalName == "xmlns" && string.IsNullOrEmpty(attribute.NamespaceUri) && attribute.Value == namespaceUri) + { + namespaceDeclared = true; + } + } + + if (!namespaceDeclared) + { + _attributes.Insert(0, new XAttributeWrapper(new XAttribute("xmlns", namespaceUri))); + } + } + } + } + + return _attributes; + } + } + + public override IXmlNode AppendChild(IXmlNode newChild) + { + IXmlNode result = base.AppendChild(newChild); + _attributes = null; + return result; } public override string Value