-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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Tests.TestObjects; using Newtonsoft.Json.Tests.TestObjects;
@@ -564,10 +565,7 @@ namespace Newtonsoft.Json.Tests.Serialization
JObject bizzaroC2 = (JObject)c2.ExtensionData["Self"]; JObject bizzaroC2 = (JObject)c2.ExtensionData["Self"];
Assert.AreEqual("Name!", (string)bizzaroC2["Name"]); Assert.AreEqual("1", (string)bizzaroC2["$ref"]);
Assert.AreEqual(1, (int)bizzaroC2["Test"]);
Assert.AreEqual(1, (int)c2.ExtensionData["Test"]);
} }
[Test] [Test]
@@ -833,5 +831,67 @@ namespace Newtonsoft.Json.Tests.Serialization
() => { JsonConvert.SerializeObject(new DocNoGetter()); }, () => { JsonConvert.SerializeObject(new DocNoGetter()); },
"Invalid extension data attribute on 'Newtonsoft.Json.Tests.Serialization.ExtensionDataTests+DocNoGetter'. Member 'Content' must have a getter."); "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 keyType = dictionaryType.GetGenericArguments()[0];
Type valueType = dictionaryType.GetGenericArguments()[1]; Type valueType = dictionaryType.GetGenericArguments()[1];
bool isJTokenValueType = typeof(JToken).IsAssignableFrom(valueType);
Type createdType; Type createdType;
@@ -524,61 +523,57 @@ namespace Newtonsoft.Json.Serialization
} }
Func<object, object> getExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(member); 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) 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; contract.ExtensionDataSetter = extensionDataSetter;
} }
if (extensionDataAttribute.WriteData) 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.ExtensionDataGetter = extensionDataGetter;
} }
contract.ExtensionDataValueType = valueType;
} }
internal struct DictionaryEnumerator<TEnumeratorKey, TEnumeratorValue> : IEnumerable<KeyValuePair<object, object>>, IEnumerator<KeyValuePair<object, object>> 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.Reflection;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Security; using System.Security;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Utilities; using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Serialization namespace Newtonsoft.Json.Serialization
@@ -144,12 +145,27 @@ namespace Newtonsoft.Json.Serialization
/// </summary> /// </summary>
public ExtensionDataGetter ExtensionDataGetter { get; set; } 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 bool? _hasRequiredOrDefaultValueProperties;
private ConstructorInfo _parametrizedConstructor; private ConstructorInfo _parametrizedConstructor;
private ConstructorInfo _overrideConstructor; private ConstructorInfo _overrideConstructor;
private ObjectConstructor<object> _overrideCreator; private ObjectConstructor<object> _overrideCreator;
private ObjectConstructor<object> _parameterizedCreator; private ObjectConstructor<object> _parameterizedCreator;
private JsonPropertyCollection _creatorParameters; private JsonPropertyCollection _creatorParameters;
private Type _extensionDataValueType;
internal bool HasRequiredOrDefaultValueProperties internal bool HasRequiredOrDefaultValueProperties
{ {
@@ -2154,7 +2154,7 @@ namespace Newtonsoft.Json.Serialization
if (extensionData != null) if (extensionData != null)
{ {
object value = CreateValueInternal(reader, null, null, null, contract, containerProperty, null); object value = ReadExtensionDataValue(contract, containerProperty, reader);
extensionData[memberName] = value; extensionData[memberName] = value;
} }
else else
@@ -2433,7 +2433,7 @@ namespace Newtonsoft.Json.Serialization
{ {
try try
{ {
object value = CreateValueInternal(reader, null, null, null, contract, member, null); object value = ReadExtensionDataValue(contract, member, reader);
contract.ExtensionDataSetter(o, memberName, value); 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) private void EndObject(object newObject, JsonReader reader, JsonObjectContract contract, int initialDepth, Dictionary<JsonProperty, PropertyPresence> propertiesPresence)
{ {
if (propertiesPresence != null) if (propertiesPresence != null)