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