From 15ea91fb63f40e7ea0a1dfb1057b949d424bb63c Mon Sep 17 00:00:00 2001 From: JamesNK Date: Fri, 22 Oct 2010 23:23:49 +0000 Subject: [PATCH] -Fixed error when deserializing nullable structs -Fixed writing JSON Schema option label -Improved error message when converting JSON with an empty property to XML --- .../Converters/XmlNodeConverterTest.cs | 86 +++++++++++++++++++ .../Schema/JsonSchemaGeneratorTests.cs | 6 +- .../Schema/JsonSchemaTests.cs | 37 ++++++++ .../Serialization/JsonSerializerTest.cs | 81 +++++++++++++++++ .../Converters/XmlNodeConverter.cs | 82 ++++++++++-------- .../Schema/JsonSchemaWriter.cs | 2 +- .../Serialization/DefaultContractResolver.cs | 40 +++++---- .../Utilities/ReflectionUtils.cs | 7 ++ 8 files changed, 283 insertions(+), 58 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs index 1865a17c..6c49c1ed 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs @@ -906,6 +906,92 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(expectedXmlJson, xmlJson); } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "XmlNodeConverter cannot convert JSON with an empty property name to XML.")] + public void EmptyPropertyName() + { + string json = @"{ + ""8452309520V2"": { + """": { + ""CLIENT"": { + ""ID_EXPIRATION_1"": { + ""VALUE"": ""12/12/2000"", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Identification Exp. Date 1"" + }, + ""ID_ISSUEDATE_1"": { + ""VALUE"": """", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Identification Issue Date 1"" + } + } + }, + ""457463534534"": { + ""ACCOUNT"": { + ""FUNDING_SOURCE"": { + ""VALUE"": ""FS0"", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Source of Funds"" + } + } + } + } +}{ + ""34534634535345"": { + """": { + ""CLIENT"": { + ""ID_NUMBER_1"": { + ""VALUE"": """", + ""DATATYPE"": ""S"", + ""MSG"": ""Missing Picture ID"" + }, + ""ID_EXPIRATION_1"": { + ""VALUE"": ""12/12/2000"", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Picture ID"" + }, + ""WALK_IN"": { + ""VALUE"": """", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Walk in"" + }, + ""PERSONAL_MEETING"": { + ""VALUE"": ""PM1"", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Met Client in Person"" + }, + ""ID_ISSUEDATE_1"": { + ""VALUE"": """", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Picture ID"" + }, + ""PHOTO_ID"": { + ""VALUE"": """", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Picture ID"" + }, + ""ID_TYPE_1"": { + ""VALUE"": """", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Picture ID"" + } + } + }, + ""45635624523"": { + ""ACCOUNT"": { + ""FUNDING_SOURCE"": { + ""VALUE"": ""FS1"", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Source of Funds"" + } + } + } + } +}"; + + DeserializeXmlNode(json); + } } } #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs b/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs index 8cb6a69c..eb48444e 100644 --- a/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs +++ b/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs @@ -512,15 +512,15 @@ namespace Newtonsoft.Json.Tests.Schema ""options"": [ { ""value"": 0, - ""value"": ""No"" + ""label"": ""No"" }, { ""value"": 1, - ""value"": ""Asc"" + ""label"": ""Asc"" }, { ""value"": -1, - ""value"": ""Desc"" + ""label"": ""Desc"" } ] } diff --git a/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaTests.cs b/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaTests.cs index 4051ea95..7459ce08 100644 --- a/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaTests.cs +++ b/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaTests.cs @@ -340,5 +340,42 @@ namespace Newtonsoft.Json.Tests.Schema ] }", json); } + + [Test] + public void ReadOptions() + { + JsonSchema schema = JsonSchema.Parse(@"{ + ""type"": ""object"", + ""properties"": { + ""x"": { + ""type"": ""integer"", + ""enum"": [ + 0, + 1, + -1 + ], + ""options"": [ + { + ""value"": 0, + ""label"": ""No"" + }, + { + ""value"": 1, + ""label"": ""Asc"" + }, + { + ""value"": -1, + ""label"": ""Desc"" + } + ] + } + } +}"); + + Assert.AreEqual(schema.Properties["x"].Options.Count, 3); + Assert.AreEqual(schema.Properties["x"].Options[0], "No"); + Assert.AreEqual(schema.Properties["x"].Options[1], "Asc"); + Assert.AreEqual(schema.Properties["x"].Options[-1], "Desc"); + } } } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 8178da58..5ece94df 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -3746,5 +3746,86 @@ keyword such as type of business."" Assert.AreEqual(meh.IDontWork, "meh"); } +#if !(SILVERLIGHT || PocketPC || NET20) + [DataContract] + public struct StructISerializable : ISerializable + { + private string _name; + + public StructISerializable(SerializationInfo info, StreamingContext context) + { + _name = info.GetString("Name"); + } + + [DataMember] + public string Name + { + get { return _name; } + set { _name = value; } + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Name", _name); + } + } + + [DataContract] + public class NullableStructPropertyClass + { + private StructISerializable _foo1; + private StructISerializable? _foo2; + + [DataMember] + public StructISerializable Foo1 + { + get { return _foo1; } + set { _foo1 = value; } + } + + [DataMember] + public StructISerializable? Foo2 + { + get { return _foo2; } + set { _foo2 = value; } + } + } + + [Test] + public void DeserializeNullableStruct() + { + NullableStructPropertyClass nullableStructPropertyClass = new NullableStructPropertyClass(); + nullableStructPropertyClass.Foo1 = new StructISerializable() {Name = "foo 1"}; + nullableStructPropertyClass.Foo2 = new StructISerializable() {Name = "foo 2"}; + + NullableStructPropertyClass barWithNull = new NullableStructPropertyClass(); + barWithNull.Foo1 = new StructISerializable() {Name = "foo 1"}; + barWithNull.Foo2 = null; + + //throws error on deserialization because bar1.Foo2 is of type Foo? + string s = JsonConvert.SerializeObject(nullableStructPropertyClass); + NullableStructPropertyClass deserialized = deserialize(s); + Assert.AreEqual(deserialized.Foo1.Name, "foo 1"); + Assert.AreEqual(deserialized.Foo2.Value.Name, "foo 2"); + + //no error Foo2 is null + s = JsonConvert.SerializeObject(barWithNull); + deserialized = deserialize(s); + Assert.AreEqual(deserialized.Foo1.Name, "foo 1"); + Assert.AreEqual(deserialized.Foo2, null); + } + + + static NullableStructPropertyClass deserialize(string serStr) + { + return JsonConvert.DeserializeObject( + serStr, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore + }); + } +#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs index c73050c1..e8b5c229 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -1118,6 +1118,9 @@ namespace Newtonsoft.Json.Converters private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager) { + if (string.IsNullOrEmpty(propertyName)) + throw new JsonSerializationException("XmlNodeConverter cannot convert JSON with an empty property name to XML."); + Dictionary attributeNameValues = ReadAttributeElements(reader, manager); string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName); @@ -1214,50 +1217,59 @@ namespace Newtonsoft.Json.Converters case JsonToken.PropertyName: string attributeName = reader.Value.ToString(); string attributeValue; - char firstChar = attributeName[0]; - switch (firstChar) + if (!string.IsNullOrEmpty(attributeName)) { - case '@': - attributeName = attributeName.Substring(1); - reader.Read(); - attributeValue = reader.Value.ToString(); - attributeNameValues.Add(attributeName, attributeValue); + char firstChar = attributeName[0]; - string namespacePrefix; - if (IsNamespaceAttribute(attributeName, out namespacePrefix)) - { - manager.AddNamespace(namespacePrefix, attributeValue); - } - break; - case '$': - attributeName = attributeName.Substring(1); - reader.Read(); - attributeValue = reader.Value.ToString(); + switch (firstChar) + { + case '@': + attributeName = attributeName.Substring(1); + reader.Read(); + attributeValue = reader.Value.ToString(); + attributeNameValues.Add(attributeName, attributeValue); - // 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) + string namespacePrefix; + if (IsNamespaceAttribute(attributeName, out namespacePrefix)) { - i = i.GetValueOrDefault() + 1; + manager.AddNamespace(namespacePrefix, attributeValue); } - jsonPrefix = "json" + i; + break; + case '$': + attributeName = attributeName.Substring(1); + reader.Read(); + attributeValue = reader.Value.ToString(); - attributeNameValues.Add("xmlns:" + jsonPrefix, JsonNamespaceUri); - manager.AddNamespace(jsonPrefix, JsonNamespaceUri); - } + // 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(jsonPrefix + ":" + attributeName, attributeValue); - break; - default: - finishedAttributes = true; - break; + attributeNameValues.Add("xmlns:" + jsonPrefix, JsonNamespaceUri); + manager.AddNamespace(jsonPrefix, JsonNamespaceUri); + } + + attributeNameValues.Add(jsonPrefix + ":" + attributeName, attributeValue); + break; + default: + finishedAttributes = true; + break; + } } + else + { + finishedAttributes = true; + } + break; case JsonToken.EndObject: finishedElement = true; diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaWriter.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaWriter.cs index 2d5d2a68..c3f9d508 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaWriter.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaWriter.cs @@ -136,7 +136,7 @@ namespace Newtonsoft.Json.Schema option.Key.WriteTo(_writer); if (option.Value != null) { - _writer.WritePropertyName(JsonSchemaConstants.OptionValuePropertyName); + _writer.WritePropertyName(JsonSchemaConstants.OptionLabelPropertyName); _writer.WriteValue(option.Value); } _writer.WriteEndObject(); diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index b840b5a0..edc91df4 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -486,38 +486,40 @@ namespace Newtonsoft.Json.Serialization /// A for the given type. protected virtual JsonContract CreateContract(Type objectType) { - if (JsonConvert.IsJsonPrimitiveType(objectType)) - return CreatePrimitiveContract(objectType); + Type t = ReflectionUtils.EnsureNotNullableType(objectType); - if (JsonTypeReflector.GetJsonObjectAttribute(objectType) != null) - return CreateObjectContract(objectType); + if (JsonConvert.IsJsonPrimitiveType(t)) + return CreatePrimitiveContract(t); - if (JsonTypeReflector.GetJsonArrayAttribute(objectType) != null) - return CreateArrayContract(objectType); + if (JsonTypeReflector.GetJsonObjectAttribute(t) != null) + return CreateObjectContract(t); - if (objectType.IsSubclassOf(typeof(JToken))) - return CreateLinqContract(objectType); + if (JsonTypeReflector.GetJsonArrayAttribute(t) != null) + return CreateArrayContract(t); - if (CollectionUtils.IsDictionaryType(objectType)) - return CreateDictionaryContract(objectType); + if (t.IsSubclassOf(typeof(JToken))) + return CreateLinqContract(t); - if (typeof(IEnumerable).IsAssignableFrom(objectType)) - return CreateArrayContract(objectType); + if (CollectionUtils.IsDictionaryType(t)) + return CreateDictionaryContract(t); - if (CanConvertToString(objectType)) - return CreateStringContract(objectType); + if (typeof(IEnumerable).IsAssignableFrom(t)) + return CreateArrayContract(t); + + if (CanConvertToString(t)) + return CreateStringContract(t); #if !SILVERLIGHT && !PocketPC - if (typeof(ISerializable).IsAssignableFrom(objectType)) - return CreateISerializableContract(objectType); + if (typeof(ISerializable).IsAssignableFrom(t)) + return CreateISerializableContract(t); #endif #if !(NET35 || NET20 || SILVERLIGHT) - if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(objectType)) - return CreateDynamicContract(objectType); + if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(t)) + return CreateDynamicContract(t); #endif - return CreateObjectContract(objectType); + return CreateObjectContract(t); } internal static bool CanConvertToString(Type type) diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index e2bb83b1..d5f557a9 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -169,6 +169,13 @@ namespace Newtonsoft.Json.Utilities return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)); } + public static Type EnsureNotNullableType(Type t) + { + return (IsNullableType(t)) + ? Nullable.GetUnderlyingType(t) + : t; + } + //public static bool IsValueTypeUnitializedValue(ValueType value) //{ // if (value == null)