-Fixed deserializing to a type when setting JToken extension data

This commit is contained in:
James Newton-King
2015-12-05 13:01:46 +13:00
parent eb0cbd6a75
commit 92102f9a33
4 changed files with 137 additions and 52 deletions
@@ -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<string, JToken> ExtensionData;
public IEnumerable<string> 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<Item>(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<string, JToken> ExtensionData;
public ItemWithConstructor(string temp)
{
}
public IEnumerable<string> 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<Item>(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]);
}
}
}
@@ -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<object, object> getExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(member);
Action<object, object> setExtensionDataDictionary = (ReflectionUtils.CanSetMemberValue(member, true, false))
? JsonTypeReflector.ReflectionDelegateFactory.CreateSet<object>(member)
: null;
Func<object> createExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor<object>(createdType);
MethodInfo addMethod = t.GetMethod("Add", new[] { keyType, valueType });
MethodCall<object, object> setExtensionDataDictionaryValue = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(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<object> createEnumerableWrapper = JsonTypeReflector.ReflectionDelegateFactory.CreateParameterizedConstructor(constructors);
ExtensionDataGetter extensionDataGetter = o =>
{
object dictionary = getExtensionDataDictionary(o);
if (dictionary == null)
{
return null;
}
return (IEnumerable<KeyValuePair<object, object>>)createEnumerableWrapper(dictionary);
};
if (extensionDataAttribute.ReadData)
{
Action<object, object> setExtensionDataDictionary = (ReflectionUtils.CanSetMemberValue(member, true, false))
? JsonTypeReflector.ReflectionDelegateFactory.CreateSet<object>(member)
: null;
Func<object> createExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor<object>(createdType);
MethodInfo addMethod = t.GetMethod("Add", new[] { keyType, valueType });
MethodCall<object, object> setExtensionDataDictionaryValue = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(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<object> createEnumerableWrapper = JsonTypeReflector.ReflectionDelegateFactory.CreateParameterizedConstructor(constructors);
ExtensionDataGetter extensionDataGetter = o =>
{
object dictionary = getExtensionDataDictionary(o);
if (dictionary == null)
{
return null;
}
return (IEnumerable<KeyValuePair<object, object>>)createEnumerableWrapper(dictionary);
};
contract.ExtensionDataGetter = extensionDataGetter;
}
contract.ExtensionDataValueType = valueType;
}
internal struct DictionaryEnumerator<TEnumeratorKey, TEnumeratorValue> : IEnumerable<KeyValuePair<object, object>>, IEnumerator<KeyValuePair<object, object>>
@@ -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
/// </summary>
public ExtensionDataGetter ExtensionDataGetter { get; set; }
/// <summary>
/// Gets or sets the extension data value type.
/// </summary>
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<object> _overrideCreator;
private ObjectConstructor<object> _parameterizedCreator;
private JsonPropertyCollection _creatorParameters;
private Type _extensionDataValueType;
internal bool HasRequiredOrDefaultValueProperties
{
@@ -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<JsonProperty, PropertyPresence> propertiesPresence)
{
if (propertiesPresence != null)