From f9b34390f507fff120bb6a680bf0afeea5b3f3a3 Mon Sep 17 00:00:00 2001 From: JamesNK Date: Sat, 16 Jan 2010 06:10:10 +0000 Subject: [PATCH] -Fix deserializing empty BSON structures -Fix error message when attempting to deserialize custom dictionary with no default constructor -Fix serializing private base members marked with JsonProperty attribute -Fix performance issue when deserializing to object member --- .../Bson/BsonReaderTests.cs | 50 +++++++++++ .../JsonTextReaderTest.cs | 24 +++++ .../Serialization/JsonSerializerTest.cs | 72 +++++++++++++++ Src/Newtonsoft.Json/Bson/BsonReader.cs | 9 +- .../JsonSerializerInternalReader.cs | 14 ++- Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 3 + .../Utilities/ReflectionUtils.cs | 88 +++++++++++++++---- 7 files changed, 232 insertions(+), 28 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs b/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs index fa2f6da7..e23c8a3e 100644 --- a/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs +++ b/Src/Newtonsoft.Json.Tests/Bson/BsonReaderTests.cs @@ -596,5 +596,55 @@ namespace Newtonsoft.Json.Tests.Bson Assert.IsFalse(reader.Read()); } + + [Test] + public void WriteAndReadEmptyListsAndDictionaries() + { + MemoryStream ms = new MemoryStream(); + BsonWriter writer = new BsonWriter(ms); + + writer.WriteStartObject(); + writer.WritePropertyName("Arguments"); + writer.WriteStartObject(); + writer.WriteEndObject(); + writer.WritePropertyName("List"); + writer.WriteStartArray(); + writer.WriteEndArray(); + writer.WriteEndObject(); + + string bson = BitConverter.ToString(ms.ToArray()); + + Assert.AreEqual("20-00-00-00-03-41-72-67-75-6D-65-6E-74-73-00-05-00-00-00-00-04-4C-69-73-74-00-05-00-00-00-00-00", bson); + + BsonReader reader = new BsonReader(new MemoryStream(MiscellaneousUtils.HexToBytes(bson))); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + Assert.AreEqual("Arguments", reader.Value.ToString()); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + Assert.AreEqual("List", reader.Value.ToString()); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndArray, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + + Assert.IsFalse(reader.Read()); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index adc5ee86..ddacda56 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -647,5 +647,29 @@ bye", reader.Value); Assert.IsFalse(jsonReader.Read()); } + + [Test] + public void ReadUnicode() + { + string json = @"{""Message"":""Hi,I\u0092ve send you smth""}"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + Assert.AreEqual("Message", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.String, reader.TokenType); + Assert.AreEqual(@"Hi,I" + '\u0092' + "ve send you smth", reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + + Assert.IsFalse(reader.Read()); + } } } \ 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 c1ba2024..8b30bb77 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -2232,5 +2232,77 @@ keyword such as type of business."" deserialized = JsonConvert.DeserializeObject("{value:'3'}"); Assert.AreEqual(@"""3""", deserialized.Value.ToString()); } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Unable to find a default constructor to use for type Newtonsoft.Json.Tests.Serialization.JsonSerializerTest+DictionaryWithNoDefaultConstructor.")] + public void DeserializeDictionaryWithNoDefaultConstructor() + { + string json = "{key1:'value',key2:'value',key3:'value'}"; + JsonConvert.DeserializeObject(json); + } + + public class DictionaryWithNoDefaultConstructor : Dictionary + { + public DictionaryWithNoDefaultConstructor(IEnumerable> initial) + { + foreach (KeyValuePair pair in initial) + { + Add(pair.Key, pair.Value); + } + } + } + + [JsonObject(MemberSerialization.OptIn)] + public class A + { + [JsonProperty("A1")] + private string _A1; + public string A1 { get { return _A1; } set { _A1 = value; } } + + [JsonProperty("A2")] + private string A2 { get; set; } + } + + [JsonObject(MemberSerialization.OptIn)] + public class B : A + { + public string B1 { get; set; } + + [JsonProperty("B2")] + string _B2; + public string B2 { get { return _B2; } set { _B2 = value; } } + + [JsonProperty("B3")] + private string B3 { get; set; } + } + + [Test] + public void SerializeNonPublicBaseJsonProperties() + { + B value = new B(); + string json = JsonConvert.SerializeObject(value, Formatting.Indented); + + Assert.AreEqual(@"{ + ""B2"": null, + ""A1"": null, + ""B3"": null, + ""A2"": null +}", json); + } + + public class TestClass + { + public string Key { get; set; } + public object Value { get; set; } + } + + [Test] + public void DeserializeToObjectProperty() + { + var json = "{ Key: 'abc', Value: 123 }"; + var item = JsonConvert.DeserializeObject(json); + + Assert.AreEqual(123, item.Value); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index 7d5ebbe5..60b0b5ea 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -265,11 +265,6 @@ namespace Newtonsoft.Json.Bson newContext.Length = ReadInt32(); return true; } - case State.ObjectStart: - { - SetToken(JsonToken.PropertyName, ReadElement()); - return true; - } case State.Complete: case State.Closed: return false; @@ -278,10 +273,8 @@ namespace Newtonsoft.Json.Bson ReadType(_currentElementType); return true; } + case State.ObjectStart: case State.ArrayStart: - ReadElement(); - ReadType(_currentElementType); - return true; case State.PostValue: ContainerContext context = _currentContext; if (context == null) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 5ad164da..a489efa7 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -465,11 +465,19 @@ namespace Newtonsoft.Json.Serialization private object CreateAndPopulateDictionary(JsonReader reader, JsonDictionaryContract contract, string id) { - IWrappedDictionary dictionary = contract.CreateWrapper(contract.DefaultCreator()); + object dictionary; - PopulateDictionary(dictionary, reader, contract, id); + if (contract.DefaultCreator != null && + (!contract.DefaultCreatorNonPublic || Serializer.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor)) + dictionary = contract.DefaultCreator(); + else + throw new JsonSerializationException("Unable to find a default constructor to use for type {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); - return dictionary.UnderlyingDictionary; + IWrappedDictionary dictionaryWrapper = contract.CreateWrapper(dictionary); + + PopulateDictionary(dictionaryWrapper, reader, contract, id); + + return dictionaryWrapper.UnderlyingDictionary; } private object PopulateDictionary(IWrappedDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, string id) diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index 65de5bce..a50b4faf 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -315,6 +315,9 @@ namespace Newtonsoft.Json.Utilities { object convertedValue; + if (targetType == typeof(object)) + return initialValue; + if (initialValue == null && ReflectionUtils.IsNullable(targetType)) return null; diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 2c4d6cc4..c6e05b96 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -25,10 +25,8 @@ using System; using System.Collections.Generic; -using System.Text; using System.Reflection; using System.Collections; -using System.ComponentModel; using System.Linq; using System.Globalization; @@ -240,10 +238,10 @@ namespace Newtonsoft.Json.Utilities if (!genericClassDefinition.IsClass || !genericClassDefinition.IsGenericTypeDefinition) throw new ArgumentNullException("'{0}' is not a generic class definition.".FormatWith(CultureInfo.InvariantCulture, genericClassDefinition)); - return InheritsGenericDefinitionInternal(type, type, genericClassDefinition, out implementingType); + return InheritsGenericDefinitionInternal(type, genericClassDefinition, out implementingType); } - private static bool InheritsGenericDefinitionInternal(Type initialType, Type currentType, Type genericClassDefinition, out Type implementingType) + private static bool InheritsGenericDefinitionInternal(Type currentType, Type genericClassDefinition, out Type implementingType) { if (currentType.IsGenericType) { @@ -262,7 +260,7 @@ namespace Newtonsoft.Json.Utilities return false; } - return InheritsGenericDefinitionInternal(initialType, currentType.BaseType, genericClassDefinition, out implementingType); + return InheritsGenericDefinitionInternal(currentType.BaseType, genericClassDefinition, out implementingType); } /// @@ -538,8 +536,8 @@ namespace Newtonsoft.Json.Utilities { List targetMembers = new List(); - targetMembers.AddRange(type.GetFields(bindingAttr)); - targetMembers.AddRange(type.GetProperties(bindingAttr)); + targetMembers.AddRange(GetFields(type, bindingAttr)); + targetMembers.AddRange(GetProperties(type, bindingAttr)); // for some reason .NET returns multiple members when overriding a generic member on a base class // http://forums.msdn.microsoft.com/en-US/netfxbcl/thread/b5abbfee-e292-4a64-8907-4e3f0fb90cd9/ @@ -622,12 +620,12 @@ namespace Newtonsoft.Json.Utilities public static object CreateGeneric(Type genericTypeDefinition, Type innerType, params object[] args) { - return CreateGeneric(genericTypeDefinition, new Type[] { innerType }, args); + return CreateGeneric(genericTypeDefinition, new [] { innerType }, args); } public static object CreateGeneric(Type genericTypeDefinition, IList innerTypes, params object[] args) { - return CreateGeneric(genericTypeDefinition, innerTypes, (t, a) => ReflectionUtils.CreateInstance(t, a.ToArray()), args); + return CreateGeneric(genericTypeDefinition, innerTypes, (t, a) => CreateInstance(t, a.ToArray()), args); } public static object CreateGeneric(Type genericTypeDefinition, IList innerTypes, Func, object> instanceCreator, params object[] args) @@ -641,16 +639,16 @@ namespace Newtonsoft.Json.Utilities return instanceCreator(specificType, args); } - public static bool IsCompatibleValue(object value, Type type) - { - if (value == null && IsNullable(type)) - return true; + public static bool IsCompatibleValue(object value, Type type) + { + if (value == null) + return IsNullable(type); - if (type.IsAssignableFrom(value.GetType())) - return true; + if (type.IsAssignableFrom(value.GetType())) + return true; - return false; - } + return false; + } public static object CreateInstance(Type type, params object[] args) { @@ -730,5 +728,61 @@ namespace Newtonsoft.Json.Utilities return null; } + + public static IEnumerable GetFields(Type targetType, BindingFlags bindingAttr) + { + ValidationUtils.ArgumentNotNull(targetType, "targetType"); + + List fieldInfos = new List(targetType.GetFields(bindingAttr)); + GetChildPrivateFields(fieldInfos, targetType, bindingAttr); + + return fieldInfos.Cast(); + } + + private static void GetChildPrivateFields(IList initialFields, Type targetType, BindingFlags bindingAttr) + { + // fix weirdness with FieldInfos only being returned for the current Type + // find base type fields and add them to result + if ((bindingAttr & BindingFlags.NonPublic) != 0) + { + // modify flags to not search for public fields + BindingFlags nonPublicBindingAttr = ((bindingAttr & BindingFlags.Public) == BindingFlags.Public) + ? bindingAttr ^ BindingFlags.Public + : bindingAttr; + + while ((targetType = targetType.BaseType) != null) + { + initialFields.AddRange(targetType.GetFields(nonPublicBindingAttr)); + } + } + } + + public static IEnumerable GetProperties(Type targetType, BindingFlags bindingAttr) + { + ValidationUtils.ArgumentNotNull(targetType, "targetType"); + + List propertyInfos = new List(targetType.GetProperties(bindingAttr)); + GetChildPrivateProperties(propertyInfos, targetType, bindingAttr); + + return propertyInfos.Cast(); + } + + private static void GetChildPrivateProperties(IList initialFields, Type targetType, BindingFlags bindingAttr) + { + // fix weirdness with FieldInfos only being returned for the current Type + // find base type fields and add them to result + if ((bindingAttr & BindingFlags.NonPublic) != 0) + { + // modify flags to not search for public fields + BindingFlags nonPublicBindingAttr = ((bindingAttr & BindingFlags.Public) == BindingFlags.Public) + ? bindingAttr ^ BindingFlags.Public + : bindingAttr; + + while ((targetType = targetType.BaseType) != null) + { + initialFields.AddRange(targetType.GetProperties(nonPublicBindingAttr)); + } + } + } } } \ No newline at end of file