From 92102f9a3352ca52d2bbbf0cd1eadae0783970ea Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 5 Dec 2015 13:01:46 +1300 Subject: [PATCH] -Fixed deserializing to a type when setting JToken extension data --- .../Serialization/ExtensionDataTests.cs | 68 ++++++++++++++- .../Serialization/DefaultContractResolver.cs | 87 +++++++++---------- .../Serialization/JsonObjectContract.cs | 16 ++++ .../JsonSerializerInternalReader.cs | 18 +++- 4 files changed, 137 insertions(+), 52 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs index fc51d7bc..6acc24df 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ExtensionDataTests.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Tests.TestObjects; @@ -564,10 +565,7 @@ namespace Newtonsoft.Json.Tests.Serialization JObject bizzaroC2 = (JObject)c2.ExtensionData["Self"]; - Assert.AreEqual("Name!", (string)bizzaroC2["Name"]); - Assert.AreEqual(1, (int)bizzaroC2["Test"]); - - Assert.AreEqual(1, (int)c2.ExtensionData["Test"]); + Assert.AreEqual("1", (string)bizzaroC2["$ref"]); } [Test] @@ -833,5 +831,67 @@ namespace Newtonsoft.Json.Tests.Serialization () => { JsonConvert.SerializeObject(new DocNoGetter()); }, "Invalid extension data attribute on 'Newtonsoft.Json.Tests.Serialization.ExtensionDataTests+DocNoGetter'. Member 'Content' must have a getter."); } + + public class Item + { + [JsonExtensionData] + public IDictionary ExtensionData; + + public IEnumerable Foo + { + get { yield return "foo"; yield return "bar"; } + } + } + + [Test] + public void Deserialize_WriteJsonDirectlyToJToken() + { + JsonSerializer jsonSerializer = new JsonSerializer + { + TypeNameHandling = TypeNameHandling.Auto + }; + StringWriter stringWriter = new StringWriter(); + jsonSerializer.Serialize(stringWriter, new Item()); + string str = stringWriter.GetStringBuilder().ToString(); + Item deserialize = jsonSerializer.Deserialize(new JsonTextReader(new StringReader(str))); + + JToken value = deserialize.ExtensionData["Foo"]["$type"]; + Assert.AreEqual(JTokenType.String, value.Type); + Assert.AreEqual("foo", (string)deserialize.ExtensionData["Foo"]["$values"][0]); + Assert.AreEqual("bar", (string)deserialize.ExtensionData["Foo"]["$values"][1]); + } + + public class ItemWithConstructor + { + [JsonExtensionData] + public IDictionary ExtensionData; + + public ItemWithConstructor(string temp) + { + } + + public IEnumerable Foo + { + get { yield return "foo"; yield return "bar"; } + } + } + + [Test] + public void DeserializeWithConstructor_WriteJsonDirectlyToJToken() + { + JsonSerializer jsonSerializer = new JsonSerializer + { + TypeNameHandling = TypeNameHandling.Auto + }; + StringWriter stringWriter = new StringWriter(); + jsonSerializer.Serialize(stringWriter, new ItemWithConstructor(null)); + string str = stringWriter.GetStringBuilder().ToString(); + Item deserialize = jsonSerializer.Deserialize(new JsonTextReader(new StringReader(str))); + + JToken value = deserialize.ExtensionData["Foo"]["$type"]; + Assert.AreEqual(JTokenType.String, value.Type); + Assert.AreEqual("foo", (string)deserialize.ExtensionData["Foo"]["$values"][0]); + Assert.AreEqual("bar", (string)deserialize.ExtensionData["Foo"]["$values"][1]); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 58e046e1..ce8be0cb 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -509,7 +509,6 @@ namespace Newtonsoft.Json.Serialization Type keyType = dictionaryType.GetGenericArguments()[0]; Type valueType = dictionaryType.GetGenericArguments()[1]; - bool isJTokenValueType = typeof(JToken).IsAssignableFrom(valueType); Type createdType; @@ -524,61 +523,57 @@ namespace Newtonsoft.Json.Serialization } Func getExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(member); - Action setExtensionDataDictionary = (ReflectionUtils.CanSetMemberValue(member, true, false)) - ? JsonTypeReflector.ReflectionDelegateFactory.CreateSet(member) - : null; - Func createExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor(createdType); - MethodInfo addMethod = t.GetMethod("Add", new[] { keyType, valueType }); - MethodCall setExtensionDataDictionaryValue = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(addMethod); - - ExtensionDataSetter extensionDataSetter = (o, key, value) => - { - object dictionary = getExtensionDataDictionary(o); - if (dictionary == null) - { - if (setExtensionDataDictionary == null) - { - throw new JsonSerializationException("Cannot set value onto extension data member '{0}'. The extension data collection is null and it cannot be set.".FormatWith(CultureInfo.InvariantCulture, member.Name)); - } - - dictionary = createExtensionDataDictionary(); - setExtensionDataDictionary(o, dictionary); - } - - // convert object value to JToken so it is compatible with dictionary - // could happen because of primitive types, type name handling and references - if (isJTokenValueType && !(value is JToken)) - { - value = (value != null) ? JToken.FromObject(value) : JValue.CreateNull(); - } - - setExtensionDataDictionaryValue(dictionary, key, value); - }; - - Type enumerableWrapper = typeof(DictionaryEnumerator<,>).MakeGenericType(keyType, valueType); - ConstructorInfo constructors = enumerableWrapper.GetConstructors().First(); - ObjectConstructor createEnumerableWrapper = JsonTypeReflector.ReflectionDelegateFactory.CreateParameterizedConstructor(constructors); - - ExtensionDataGetter extensionDataGetter = o => - { - object dictionary = getExtensionDataDictionary(o); - if (dictionary == null) - { - return null; - } - - return (IEnumerable>)createEnumerableWrapper(dictionary); - }; if (extensionDataAttribute.ReadData) { + Action setExtensionDataDictionary = (ReflectionUtils.CanSetMemberValue(member, true, false)) + ? JsonTypeReflector.ReflectionDelegateFactory.CreateSet(member) + : null; + Func createExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor(createdType); + MethodInfo addMethod = t.GetMethod("Add", new[] { keyType, valueType }); + MethodCall setExtensionDataDictionaryValue = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(addMethod); + + ExtensionDataSetter extensionDataSetter = (o, key, value) => + { + object dictionary = getExtensionDataDictionary(o); + if (dictionary == null) + { + if (setExtensionDataDictionary == null) + { + throw new JsonSerializationException("Cannot set value onto extension data member '{0}'. The extension data collection is null and it cannot be set.".FormatWith(CultureInfo.InvariantCulture, member.Name)); + } + + dictionary = createExtensionDataDictionary(); + setExtensionDataDictionary(o, dictionary); + } + + setExtensionDataDictionaryValue(dictionary, key, value); + }; + contract.ExtensionDataSetter = extensionDataSetter; } if (extensionDataAttribute.WriteData) { + Type enumerableWrapper = typeof(DictionaryEnumerator<,>).MakeGenericType(keyType, valueType); + ConstructorInfo constructors = enumerableWrapper.GetConstructors().First(); + ObjectConstructor createEnumerableWrapper = JsonTypeReflector.ReflectionDelegateFactory.CreateParameterizedConstructor(constructors); + + ExtensionDataGetter extensionDataGetter = o => + { + object dictionary = getExtensionDataDictionary(o); + if (dictionary == null) + { + return null; + } + + return (IEnumerable>)createEnumerableWrapper(dictionary); + }; + contract.ExtensionDataGetter = extensionDataGetter; } + + contract.ExtensionDataValueType = valueType; } internal struct DictionaryEnumerator : IEnumerable>, IEnumerator> diff --git a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs index 1b33569a..dfed1093 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs @@ -28,6 +28,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.Serialization; using System.Security; +using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Serialization @@ -144,12 +145,27 @@ namespace Newtonsoft.Json.Serialization /// public ExtensionDataGetter ExtensionDataGetter { get; set; } + /// + /// Gets or sets the extension data value type. + /// + public Type ExtensionDataValueType + { + get { return _extensionDataValueType; } + set + { + _extensionDataValueType = value; + ExtensionDataIsJToken = (value != null && typeof(JToken).IsAssignableFrom(value)); + } + } + + internal bool ExtensionDataIsJToken; private bool? _hasRequiredOrDefaultValueProperties; private ConstructorInfo _parametrizedConstructor; private ConstructorInfo _overrideConstructor; private ObjectConstructor _overrideCreator; private ObjectConstructor _parameterizedCreator; private JsonPropertyCollection _creatorParameters; + private Type _extensionDataValueType; internal bool HasRequiredOrDefaultValueProperties { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 05f027c9..3d4b32a3 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -2154,7 +2154,7 @@ namespace Newtonsoft.Json.Serialization if (extensionData != null) { - object value = CreateValueInternal(reader, null, null, null, contract, containerProperty, null); + object value = ReadExtensionDataValue(contract, containerProperty, reader); extensionData[memberName] = value; } else @@ -2433,7 +2433,7 @@ namespace Newtonsoft.Json.Serialization { try { - object value = CreateValueInternal(reader, null, null, null, contract, member, null); + object value = ReadExtensionDataValue(contract, member, reader); contract.ExtensionDataSetter(o, memberName, value); } @@ -2448,6 +2448,20 @@ namespace Newtonsoft.Json.Serialization } } + private object ReadExtensionDataValue(JsonObjectContract contract, JsonProperty member, JsonReader reader) + { + object value; + if (contract.ExtensionDataIsJToken) + { + value = JToken.ReadFrom(reader); + } + else + { + value = CreateValueInternal(reader, null, null, null, contract, member, null); + } + return value; + } + private void EndObject(object newObject, JsonReader reader, JsonObjectContract contract, int initialDepth, Dictionary propertiesPresence) { if (propertiesPresence != null)