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))