diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs
index 24a93032..f6b35fab 100644
--- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs
+++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs
@@ -25,6 +25,7 @@
#if !SILVERLIGHT
using System;
+using Newtonsoft.Json.Tests.Serialization;
using NUnit.Framework;
using Newtonsoft.Json;
using System.IO;
@@ -71,12 +72,19 @@ namespace Newtonsoft.Json.Tests.Converters
}
private XmlNode DeserializeXmlNode(string json)
+ {
+ return DeserializeXmlNode(json, null);
+ }
+
+ private XmlNode DeserializeXmlNode(string json, string deserializeRootElementName)
{
JsonTextReader reader;
reader = new JsonTextReader(new StringReader(json));
reader.Read();
XmlNodeConverter converter = new XmlNodeConverter();
+ if (deserializeRootElementName != null)
+ converter.DeserializeRootElementName = deserializeRootElementName;
XmlNode node = (XmlNode)converter.ReadJson(reader, typeof (XmlDocument), new JsonSerializer());
@@ -848,6 +856,56 @@ namespace Newtonsoft.Json.Tests.Converters
".Replace(Environment.NewLine, string.Empty), doc.InnerXml);
}
+
+ [Test]
+ public void SerializeDeserializeSpecialProperties()
+ {
+ PreserveReferencesHandlingTests.CircularDictionary circularDictionary = new PreserveReferencesHandlingTests.CircularDictionary();
+ circularDictionary.Add("other", new PreserveReferencesHandlingTests.CircularDictionary { { "blah", null } });
+ circularDictionary.Add("self", circularDictionary);
+
+ string json = JsonConvert.SerializeObject(circularDictionary, Formatting.Indented,
+ new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All });
+
+ Assert.AreEqual(@"{
+ ""$id"": ""1"",
+ ""other"": {
+ ""$id"": ""2"",
+ ""blah"": null
+ },
+ ""self"": {
+ ""$ref"": ""1""
+ }
+}", json);
+
+ XmlNode node = DeserializeXmlNode(json, "root");
+ string xml = GetIndentedInnerXml(node);
+ string expected = @"
+
+
+
+
+
+";
+
+ Assert.AreEqual(expected, xml);
+
+ string xmlJson = SerializeXmlNode(node);
+ string expectedXmlJson = @"{
+ ""root"": {
+ ""$id"": ""1"",
+ ""other"": {
+ ""$id"": ""2"",
+ ""blah"": null
+ },
+ ""self"": {
+ ""$ref"": ""1""
+ }
+ }
+}";
+
+ Assert.AreEqual(expectedXmlJson, xmlJson);
+ }
}
}
#endif
\ No newline at end of file
diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
index 099f5009..2c3a2587 100644
--- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
+++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
@@ -2939,5 +2939,82 @@ keyword such as type of business.""
Assert.AreEqual(testObject.Document.InnerXml, newTestObject.Document.InnerXml);
}
#endif
+
+ [Test]
+ public void FullClientMapSerialization()
+ {
+ ClientMap source = new ClientMap()
+ {
+ position = new Pos() { X = 100, Y = 200 },
+ center = new PosDouble() { X = 251.6, Y = 361.3 }
+ };
+
+ string json = JsonConvert.SerializeObject(source, new PosConverter(), new PosDoubleConverter());
+ Assert.AreEqual("{\"position\":new Pos(100,200),\"center\":new PosD(251.6,361.3)}", json);
+ }
+
+ public class ClientMap
+ {
+ public Pos position { get; set; }
+ public PosDouble center { get; set; }
+ }
+
+ public class Pos
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+
+ public class PosDouble
+ {
+ public double X { get; set; }
+ public double Y { get; set; }
+ }
+
+ public class PosConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ Pos p = (Pos)value;
+
+ if (p != null)
+ writer.WriteRawValue(String.Format("new Pos({0},{1})", p.X, p.Y));
+ else
+ writer.WriteNull();
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType.IsAssignableFrom(typeof(Pos));
+ }
+ }
+
+ public class PosDoubleConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ PosDouble p = (PosDouble)value;
+
+ if (p != null)
+ writer.WriteRawValue(String.Format("new PosD({0},{1})", p.X, p.Y));
+ else
+ writer.WriteNull();
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType.IsAssignableFrom(typeof(PosDouble));
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs
index ee5434eb..57277577 100644
--- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs
+++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs
@@ -820,7 +820,10 @@ namespace Newtonsoft.Json.Converters
switch (node.NodeType)
{
case XmlNodeType.Attribute:
- return "@" + ResolveFullName(node, manager);
+ if (node.NamespaceURI == JsonNamespaceUri)
+ return "$" + node.LocalName;
+ else
+ return "@" + ResolveFullName(node, manager);
case XmlNodeType.CDATA:
return CDataName;
case XmlNodeType.Comment:
@@ -966,9 +969,13 @@ namespace Newtonsoft.Json.Converters
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
if (node.NamespaceURI == "http://www.w3.org/2000/xmlns/" && node.Value == JsonNamespaceUri)
- break;
- else if (node.NamespaceURI == JsonNamespaceUri)
- break;
+ return;
+
+ if (node.NamespaceURI == JsonNamespaceUri)
+ {
+ if (node.LocalName == "Array")
+ return;
+ }
if (writePropertyName)
writer.WritePropertyName(GetPropertyName(node, manager));
@@ -1042,18 +1049,20 @@ namespace Newtonsoft.Json.Converters
throw new JsonSerializationException("Unexpected type when converting XML: " + objectType);
}
- if (!string.IsNullOrEmpty(DeserializeRootElementName))
- {
- rootNode = document.CreateElement(DeserializeRootElementName);
- document.AppendChild(rootNode);
- }
-
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException("XmlNodeConverter can only convert JSON that begins with an object.");
- reader.Read();
-
- DeserializeNode(reader, document, manager, rootNode);
+ if (!string.IsNullOrEmpty(DeserializeRootElementName))
+ {
+ //rootNode = document.CreateElement(DeserializeRootElementName);
+ //document.AppendChild(rootNode);
+ ReadElement(reader, document, rootNode, DeserializeRootElementName, manager);
+ }
+ else
+ {
+ reader.Read();
+ DeserializeNode(reader, document, manager, rootNode);
+ }
#if !NET20
if (objectType == typeof(XElement))
@@ -1088,169 +1097,217 @@ namespace Newtonsoft.Json.Converters
// processing instructions and the xml declaration start with ?
if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?')
{
- if (propertyName == DeclarationName)
- {
- 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 declaration = document.CreateXmlDeclaration(version, encoding, standalone);
- currentNode.AppendChild(declaration);
- }
- else
- {
- IXmlNode instruction = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString());
- currentNode.AppendChild(instruction);
- }
+ CreateInstruction(reader, document, currentNode, propertyName);
}
else
{
- // deserialize xml element
- bool finishedAttributes = false;
- bool finishedElement = false;
- string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);
-
- Dictionary attributeNameValues = new Dictionary();
-
if (reader.TokenType == JsonToken.StartArray)
{
- IXmlElement nestedArrayElement = CreateElement(propertyName, document, elementPrefix, manager);
-
- currentNode.AppendChild(nestedArrayElement);
-
- while (reader.Read() && reader.TokenType != JsonToken.EndArray)
- {
- DeserializeValue(reader, document, manager, propertyName, nestedArrayElement);
- }
-
+ ReadArrayElements(reader, document, propertyName, currentNode, manager);
return;
}
- // a string token means the element only has a single text child
- 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)
- {
- // read properties until first non-attribute is encountered
- while (!finishedAttributes && !finishedElement && reader.Read())
- {
- switch (reader.TokenType)
- {
- case JsonToken.PropertyName:
- string attributeName = reader.Value.ToString();
-
- if (attributeName[0] == '@')
- {
- attributeName = attributeName.Substring(1);
- reader.Read();
- string attributeValue = reader.Value.ToString();
- attributeNameValues.Add(attributeName, attributeValue);
-
- string namespacePrefix;
-
- if (IsNamespaceAttribute(attributeName, out namespacePrefix))
- {
- manager.AddNamespace(namespacePrefix, attributeValue);
- }
- }
- else
- {
- finishedAttributes = true;
- }
- break;
- case JsonToken.EndObject:
- finishedElement = true;
- break;
- default:
- throw new JsonSerializationException("Unexpected JsonToken: " + reader.TokenType);
- }
- }
- }
-
// have to wait until attributes have been parsed before creating element
// attributes may contain namespace info used by the element
- IXmlElement element = CreateElement(propertyName, document, elementPrefix, manager);
-
- currentNode.AppendChild(element);
-
- // add attributes to newly created element
- foreach (KeyValuePair nameValue in attributeNameValues)
- {
- string attributePrefix = MiscellaneousUtils.GetPrefix(nameValue.Key);
-
- IXmlNode attribute = (!string.IsNullOrEmpty(attributePrefix))
- ? document.CreateAttribute(nameValue.Key, manager.LookupNamespace(attributePrefix), nameValue.Value)
- : document.CreateAttribute(nameValue.Key, nameValue.Value);
-
- element.SetAttributeNode(attribute);
- }
-
- if (reader.TokenType == JsonToken.String)
- {
- element.AppendChild(document.CreateTextNode(reader.Value.ToString()));
- }
- else if (reader.TokenType == JsonToken.Integer)
- {
- element.AppendChild(document.CreateTextNode(XmlConvert.ToString((long)reader.Value)));
- }
- else if (reader.TokenType == JsonToken.Float)
- {
- element.AppendChild(document.CreateTextNode(XmlConvert.ToString((double)reader.Value)));
- }
- else if (reader.TokenType == JsonToken.Boolean)
- {
- element.AppendChild(document.CreateTextNode(XmlConvert.ToString((bool)reader.Value)));
- }
- else if (reader.TokenType == JsonToken.Date)
- {
- DateTime d = (DateTime)reader.Value;
- element.AppendChild(document.CreateTextNode(XmlConvert.ToString(d, DateTimeUtils.ToSerializationMode(d.Kind))));
- }
- else if (reader.TokenType == JsonToken.Null)
- {
- // empty element. do nothing
- }
- else
- {
- // finished element will have no children to deserialize
- if (!finishedElement)
- {
- manager.PushScope();
-
- DeserializeNode(reader, document, manager, element);
-
- manager.PopScope();
- }
- }
+ ReadElement(reader, document, currentNode, propertyName, manager);
}
break;
}
}
+ private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager)
+ {
+ Dictionary attributeNameValues = ReadAttributeElements(reader, manager);
+
+ string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);
+
+ IXmlElement element = CreateElement(propertyName, document, elementPrefix, manager);
+
+ currentNode.AppendChild(element);
+
+ // add attributes to newly created element
+ foreach (KeyValuePair nameValue in attributeNameValues)
+ {
+ string attributePrefix = MiscellaneousUtils.GetPrefix(nameValue.Key);
+
+ IXmlNode attribute = (!string.IsNullOrEmpty(attributePrefix))
+ ? document.CreateAttribute(nameValue.Key, manager.LookupNamespace(attributePrefix), nameValue.Value)
+ : document.CreateAttribute(nameValue.Key, nameValue.Value);
+
+ element.SetAttributeNode(attribute);
+ }
+
+ if (reader.TokenType == JsonToken.String)
+ {
+ element.AppendChild(document.CreateTextNode(reader.Value.ToString()));
+ }
+ else if (reader.TokenType == JsonToken.Integer)
+ {
+ element.AppendChild(document.CreateTextNode(XmlConvert.ToString((long)reader.Value)));
+ }
+ else if (reader.TokenType == JsonToken.Float)
+ {
+ element.AppendChild(document.CreateTextNode(XmlConvert.ToString((double)reader.Value)));
+ }
+ else if (reader.TokenType == JsonToken.Boolean)
+ {
+ element.AppendChild(document.CreateTextNode(XmlConvert.ToString((bool)reader.Value)));
+ }
+ else if (reader.TokenType == JsonToken.Date)
+ {
+ DateTime d = (DateTime)reader.Value;
+ element.AppendChild(document.CreateTextNode(XmlConvert.ToString(d, DateTimeUtils.ToSerializationMode(d.Kind))));
+ }
+ else if (reader.TokenType == JsonToken.Null)
+ {
+ // empty element. do nothing
+ }
+ else
+ {
+ // finished element will have no children to deserialize
+ if (reader.TokenType != JsonToken.EndObject)
+ {
+ manager.PushScope();
+
+ DeserializeNode(reader, document, manager, element);
+
+ manager.PopScope();
+ }
+ }
+ }
+
+ private void ReadArrayElements(JsonReader reader, IXmlDocument document, string propertyName, IXmlNode currentNode, XmlNamespaceManager manager)
+ {
+ string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);
+
+ IXmlElement nestedArrayElement = CreateElement(propertyName, document, elementPrefix, manager);
+
+ currentNode.AppendChild(nestedArrayElement);
+
+ while (reader.Read() && reader.TokenType != JsonToken.EndArray)
+ {
+ DeserializeValue(reader, document, manager, propertyName, nestedArrayElement);
+ }
+ }
+
+ private Dictionary ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager)
+ {
+ Dictionary attributeNameValues = new Dictionary();
+ bool finishedAttributes = false;
+ bool finishedElement = false;
+
+ // a string token means the element only has a single text child
+ 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)
+ {
+ // read properties until first non-attribute is encountered
+ while (!finishedAttributes && !finishedElement && reader.Read())
+ {
+ switch (reader.TokenType)
+ {
+ case JsonToken.PropertyName:
+ string attributeName = reader.Value.ToString();
+ string attributeValue;
+ char firstChar = attributeName[0];
+
+ switch (firstChar)
+ {
+ case '@':
+ attributeName = attributeName.Substring(1);
+ reader.Read();
+ attributeValue = reader.Value.ToString();
+ attributeNameValues.Add(attributeName, attributeValue);
+
+ string namespacePrefix;
+ if (IsNamespaceAttribute(attributeName, out namespacePrefix))
+ {
+ manager.AddNamespace(namespacePrefix, attributeValue);
+ }
+ break;
+ case '$':
+ attributeName = attributeName.Substring(1);
+ reader.Read();
+ attributeValue = reader.Value.ToString();
+
+ // check that JsonNamespaceUri is in scope
+ // if it isn't then add it to document and namespace manager
+ string jsonPrefix = manager.LookupPrefix(JsonNamespaceUri);
+ if (jsonPrefix == null)
+ {
+ // ensure that the prefix used is free
+ int? i = null;
+ while (manager.LookupNamespace("json" + i) != null)
+ {
+ i = i.GetValueOrDefault() + 1;
+ }
+ jsonPrefix = "json" + i;
+
+ attributeNameValues.Add("xmlns:" + jsonPrefix, JsonNamespaceUri);
+ manager.AddNamespace(jsonPrefix, JsonNamespaceUri);
+ }
+
+ attributeNameValues.Add(jsonPrefix + ":" + attributeName, attributeValue);
+ break;
+ default:
+ finishedAttributes = true;
+ break;
+ }
+ break;
+ case JsonToken.EndObject:
+ finishedElement = true;
+ break;
+ default:
+ throw new JsonSerializationException("Unexpected JsonToken: " + reader.TokenType);
+ }
+ }
+ }
+
+ return attributeNameValues;
+ }
+
+ private void CreateInstruction(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName)
+ {
+ if (propertyName == DeclarationName)
+ {
+ 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 declaration = document.CreateXmlDeclaration(version, encoding, standalone);
+ currentNode.AppendChild(declaration);
+ }
+ else
+ {
+ IXmlNode instruction = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString());
+ currentNode.AppendChild(instruction);
+ }
+ }
+
private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager)
{
return (!string.IsNullOrEmpty(elementPrefix))