-Added support for overriding the creation of non-concrete types

This commit is contained in:
James Newton-King
2014-05-09 18:13:20 +12:00
parent 57bd1f7daf
commit d68c9198f2
7 changed files with 509 additions and 34 deletions
@@ -235,6 +235,7 @@
<Compile Include="Serialization\ConstructorHandlingTests.cs" />
<Compile Include="Serialization\ContractResolverTests.cs" />
<Compile Include="Serialization\DefaultValueHandlingTests.cs" />
<Compile Include="Serialization\DynamicConcreteTests.cs" />
<Compile Include="Serialization\DynamicTests.cs" />
<Compile Include="Serialization\EntitiesSerializationTests.cs" />
<Compile Include="Serialization\ExtensionDataTests.cs" />
@@ -108,6 +108,155 @@ namespace Newtonsoft.Json.Tests.Serialization
[TestFixture]
public class ContractResolverTests : TestFixtureBase
{
[Test]
public void ListInterface()
{
var resolver = new DefaultContractResolver();
var contract = (JsonArrayContract)resolver.ResolveContract(typeof(IList<int>));
Assert.IsTrue(contract.IsInstantiable);
Assert.AreEqual(typeof(List<int>), contract.CreatedType);
Assert.IsNotNull(contract.DefaultCreator);
}
[Test]
public void AbstractTestClass()
{
var resolver = new DefaultContractResolver();
var contract = (JsonObjectContract)resolver.ResolveContract(typeof(AbstractTestClass));
Assert.IsFalse(contract.IsInstantiable);
Assert.IsNull(contract.DefaultCreator);
Assert.IsNull(contract.OverrideCreator);
ExceptionAssert.Throws<JsonSerializationException>(
"Could not create an instance of type Newtonsoft.Json.Tests.Serialization.AbstractTestClass. Type is an interface or abstract class and cannot be instantiated. Path 'Value', line 1, position 7.",
() => JsonConvert.DeserializeObject<AbstractTestClass>(@"{Value:'Value!'}", new JsonSerializerSettings
{
ContractResolver = resolver
}));
contract.DefaultCreator = () => new AbstractImplementationTestClass();
var o = JsonConvert.DeserializeObject<AbstractTestClass>(@"{Value:'Value!'}", new JsonSerializerSettings
{
ContractResolver = resolver
});
Assert.AreEqual("Value!", o.Value);
}
[Test]
public void AbstractListTestClass()
{
var resolver = new DefaultContractResolver();
var contract = (JsonArrayContract)resolver.ResolveContract(typeof(AbstractListTestClass<int>));
Assert.IsFalse(contract.IsInstantiable);
Assert.IsNull(contract.DefaultCreator);
Assert.IsNull(contract.ParametrizedCreator);
ExceptionAssert.Throws<JsonSerializationException>(
"Could not create an instance of type Newtonsoft.Json.Tests.Serialization.AbstractListTestClass`1[System.Int32]. Type is an interface or abstract class and cannot be instantiated. Path '', line 1, position 1.",
() => JsonConvert.DeserializeObject<AbstractListTestClass<int>>(@"[1,2]", new JsonSerializerSettings
{
ContractResolver = resolver
}));
contract.DefaultCreator = () => new AbstractImplementationListTestClass<int>();
var l = JsonConvert.DeserializeObject<AbstractListTestClass<int>>(@"[1,2]", new JsonSerializerSettings
{
ContractResolver = resolver
});
Assert.AreEqual(2, l.Count);
Assert.AreEqual(1, l[0]);
Assert.AreEqual(2, l[1]);
}
public class CustomList<T> : List<T>
{
}
[Test]
public void ListInterfaceDefaultCreator()
{
var resolver = new DefaultContractResolver();
var contract = (JsonArrayContract)resolver.ResolveContract(typeof(IList<int>));
Assert.IsTrue(contract.IsInstantiable);
Assert.IsNotNull(contract.DefaultCreator);
contract.DefaultCreator = () => new CustomList<int>();
var l = JsonConvert.DeserializeObject<IList<int>>(@"[1,2,3]", new JsonSerializerSettings
{
ContractResolver = resolver
});
Assert.AreEqual(typeof(CustomList<int>), l.GetType());
Assert.AreEqual(3, l.Count);
Assert.AreEqual(1, l[0]);
Assert.AreEqual(2, l[1]);
Assert.AreEqual(3, l[2]);
}
public class CustomDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
}
[Test]
public void DictionaryInterfaceDefaultCreator()
{
var resolver = new DefaultContractResolver();
var contract = (JsonDictionaryContract)resolver.ResolveContract(typeof(IDictionary<string, int>));
Assert.IsTrue(contract.IsInstantiable);
Assert.IsNotNull(contract.DefaultCreator);
contract.DefaultCreator = () => new CustomDictionary<string, int>();
var d = JsonConvert.DeserializeObject<IDictionary<string, int>>(@"{key1:1,key2:2}", new JsonSerializerSettings
{
ContractResolver = resolver
});
Assert.AreEqual(typeof(CustomDictionary<string, int>), d.GetType());
Assert.AreEqual(2, d.Count);
Assert.AreEqual(1, d["key1"]);
Assert.AreEqual(2, d["key2"]);
}
[Test]
public void AbstractDictionaryTestClass()
{
var resolver = new DefaultContractResolver();
var contract = (JsonDictionaryContract)resolver.ResolveContract(typeof(AbstractDictionaryTestClass<string, int>));
Assert.IsFalse(contract.IsInstantiable);
Assert.IsNull(contract.DefaultCreator);
Assert.IsNull(contract.ParametrizedCreator);
ExceptionAssert.Throws<JsonSerializationException>(
"Could not create an instance of type Newtonsoft.Json.Tests.Serialization.AbstractDictionaryTestClass`2[System.String,System.Int32]. Type is an interface or abstract class and cannot be instantiated. Path 'key1', line 1, position 6.",
() => JsonConvert.DeserializeObject<AbstractDictionaryTestClass<string, int>>(@"{key1:1,key2:2}", new JsonSerializerSettings
{
ContractResolver = resolver
}));
contract.DefaultCreator = () => new AbstractImplementationDictionaryTestClass<string, int>();
var d = JsonConvert.DeserializeObject<AbstractDictionaryTestClass<string, int>>(@"{key1:1,key2:2}", new JsonSerializerSettings
{
ContractResolver = resolver
});
Assert.AreEqual(2, d.Count);
Assert.AreEqual(1, d["key1"]);
Assert.AreEqual(2, d["key2"]);
}
[Test]
public void SerializeWithEscapedPropertyName()
{
@@ -0,0 +1,281 @@
#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System.Reflection;
using System.Reflection.Emit;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Tests.TestObjects;
using System;
using System.Linq;
#if !NETFX_CORE
using NUnit.Framework;
#else
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
#endif
namespace Newtonsoft.Json.Tests.Serialization
{
[TestFixture]
public class DynamicConcreteTests : TestFixtureBase
{
[Test]
public void UseDynamicConcreteIfTargetObjectTypeIsAnInterfaceWithNoBackingClass()
{
string json = @"{Name:""Name!""}";
var c = JsonConvert.DeserializeObject<IInterfaceWithNoConcrete>(json, new JsonSerializerSettings
{
ContractResolver = new DynamicConcreteContractResolver()
});
Assert.AreEqual("Name!", c.Name);
}
[Test]
public void UseDynamicConcreteIfTargetObjectTypeIsAnAbstractClassWithNoConcrete()
{
string json = @"{Name:""Name!"", Game:""Same""}";
var c = JsonConvert.DeserializeObject<AbstractWithNoConcrete>(json, new JsonSerializerSettings
{
ContractResolver = new DynamicConcreteContractResolver()
});
Assert.AreEqual("Name!", c.Name);
Assert.AreEqual("Same", c.Game);
}
[Test]
public void AnyMethodsExposedByDynamicConcreteAreHarmless()
{
string json = @"{Name:""Name!""}";
var c = JsonConvert.DeserializeObject<IInterfaceWithNoConcrete>(json, new JsonSerializerSettings
{
ContractResolver = new DynamicConcreteContractResolver()
});
c.FuncWithRefType(10, null);
c.FuncWithValType_1();
c.FuncWithValType_2();
}
}
public abstract class AbstractWithNoConcrete
{
public string Name { get; set; }
public abstract string Game { get; set; }
}
public interface IInterfaceWithNoConcrete
{
string Name { get; set; }
object FuncWithRefType(int a, object b);
int FuncWithValType_1();
bool FuncWithValType_2();
}
public class DynamicConcreteContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
if (contract.CreatedType.IsAbstract || contract.CreatedType.IsInterface)
contract.DefaultCreator = () => DynamicConcrete.GetInstanceFor(contract.CreatedType);
return contract;
}
}
/// <summary>
/// Creates run-time backing types for abstract classes and interfaces
/// </summary>
public static class DynamicConcrete
{
/// <summary>
/// Get an empty instance of a dynamic proxy for type T.
/// All public fields are writable and all properties have both getters and setters.
/// </summary>
public static T GetInstanceFor<T>()
{
return (T)GetInstanceFor(typeof(T));
}
static readonly ModuleBuilder ModuleBuilder;
static readonly AssemblyBuilder DynamicAssembly;
/// <summary>
/// Get an empty instance of a dynamic proxy for the given type.
/// All public fields are writable and all properties have both getters and setters.
/// </summary>
public static object GetInstanceFor(Type targetType)
{
lock (DynamicAssembly)
{
var constructedType = DynamicAssembly.GetType(ProxyName(targetType)) ?? GetConstructedType(targetType);
var instance = Activator.CreateInstance(constructedType);
return instance;
}
}
static string ProxyName(Type targetType)
{
return targetType.Name + "Proxy";
}
static DynamicConcrete()
{
var assemblyName = new AssemblyName("DynImpl");
DynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder = DynamicAssembly.DefineDynamicModule("DynImplModule");
}
static Type GetConstructedType(Type targetType)
{
var typeBuilder = ModuleBuilder.DefineType(targetType.Name + "Proxy", TypeAttributes.Public);
var ctorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
new Type[] { });
var ilGenerator = ctorBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ret);
IncludeType(targetType, typeBuilder);
foreach (var face in targetType.GetInterfaces())
IncludeType(face, typeBuilder);
return typeBuilder.CreateType();
}
static void IncludeType(Type typeOfT, TypeBuilder typeBuilder)
{
var methodInfos = typeOfT.GetMethods();
foreach (var methodInfo in methodInfos)
{
if (methodInfo.Name.StartsWith("set_")) continue; // we always add a set for a get.
if (methodInfo.Name.StartsWith("get_"))
{
BindProperty(typeBuilder, methodInfo);
}
else
{
if (methodInfo.IsAbstract) BindMethod(typeBuilder, methodInfo);
}
}
if (typeOfT.IsInterface) typeBuilder.AddInterfaceImplementation(typeOfT);
else if (typeOfT.IsAbstract) typeBuilder.SetParent(typeOfT);
}
static void BindMethod(TypeBuilder typeBuilder, MethodInfo methodInfo)
{
var args = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray();
var methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
CallingConventions.HasThis,
methodInfo.ReturnType,
args
);
var methodILGen = methodBuilder.GetILGenerator();
if (methodInfo.ReturnType == typeof(void))
{
methodILGen.Emit(OpCodes.Ret);
}
else
{
if (methodInfo.ReturnType.IsPrimitive)
{
methodILGen.Emit(OpCodes.Ldc_I4_0);
}
else if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum)
{
var getMethod = typeof(Activator).GetMethod("CreateInstance",
new[] { typeof(Type) });
var lb = methodILGen.DeclareLocal(methodInfo.ReturnType);
if (lb.LocalType != null)
{
methodILGen.Emit(OpCodes.Ldtoken, lb.LocalType);
methodILGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"));
methodILGen.Emit(OpCodes.Callvirt, getMethod);
methodILGen.Emit(OpCodes.Unbox_Any, lb.LocalType);
}
}
else
{
methodILGen.Emit(OpCodes.Ldnull);
}
methodILGen.Emit(OpCodes.Ret);
}
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
}
/// <summary>
/// Bind a new property into a type builder with getters and setters.
/// </summary>
public static void BindProperty(TypeBuilder typeBuilder, MethodInfo methodInfo)
{
// Backing Field
var propertyName = methodInfo.Name.Replace("get_", "");
var propertyType = methodInfo.ReturnType;
var backingField = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
//Getter
var backingGet = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public |
MethodAttributes.SpecialName | MethodAttributes.Virtual |
MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
var getIl = backingGet.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, backingField);
getIl.Emit(OpCodes.Ret);
//Setter
var backingSet = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public |
MethodAttributes.SpecialName | MethodAttributes.Virtual |
MethodAttributes.HideBySig, null, new[] { propertyType });
var setIl = backingSet.GetILGenerator();
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, backingField);
setIl.Emit(OpCodes.Ret);
// Property
var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null);
propertyBuilder.SetGetMethod(backingGet);
propertyBuilder.SetSetMethod(backingSet);
}
}
}
@@ -7898,6 +7898,31 @@ Parameter name: value",
}
}
public abstract class AbstractTestClass
{
public string Value { get; set; }
}
public class AbstractImplementationTestClass : AbstractTestClass
{
}
public abstract class AbstractListTestClass<T> : List<T>
{
}
public class AbstractImplementationListTestClass<T> : AbstractListTestClass<T>
{
}
public abstract class AbstractDictionaryTestClass<TKey, TValue> : Dictionary<TKey, TValue>
{
}
public class AbstractImplementationDictionaryTestClass<TKey, TValue> : AbstractDictionaryTestClass<TKey, TValue>
{
}
public class PublicConstructorOverridenByJsonConstructor
{
public string Value { get; private set; }
@@ -375,34 +375,37 @@ namespace Newtonsoft.Json.Serialization
if (attribute != null)
contract.ItemRequired = attribute._itemRequired;
ConstructorInfo overrideConstructor = GetAttributeConstructor(contract.NonNullableUnderlyingType);
if (contract.IsInstantiable)
{
ConstructorInfo overrideConstructor = GetAttributeConstructor(contract.NonNullableUnderlyingType);
// check if a JsonConstructorAttribute has been defined and use that
if (overrideConstructor != null)
{
#pragma warning disable 618
contract.OverrideConstructor = overrideConstructor;
#pragma warning restore 618
contract.CreatorParameters.AddRange(CreateConstructorParameters(overrideConstructor, contract.Properties));
}
else if (contract.MemberSerialization == MemberSerialization.Fields)
{
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
// mimic DataContractSerializer behaviour when populating fields by overriding default creator to create an uninitialized object
// note that this is only possible when the application is fully trusted so fall back to using the default constructor (if available) in partial trust
if (JsonTypeReflector.FullyTrusted)
contract.DefaultCreator = contract.GetUninitializedObject;
#endif
}
else if (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic)
{
ConstructorInfo constructor = GetParametrizedConstructor(contract.NonNullableUnderlyingType);
if (constructor != null)
// check if a JsonConstructorAttribute has been defined and use that
if (overrideConstructor != null)
{
#pragma warning disable 618
contract.ParametrizedConstructor = constructor;
contract.OverrideConstructor = overrideConstructor;
#pragma warning restore 618
contract.CreatorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties));
contract.CreatorParameters.AddRange(CreateConstructorParameters(overrideConstructor, contract.Properties));
}
else if (contract.MemberSerialization == MemberSerialization.Fields)
{
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
// mimic DataContractSerializer behaviour when populating fields by overriding default creator to create an uninitialized object
// note that this is only possible when the application is fully trusted so fall back to using the default constructor (if available) in partial trust
if (JsonTypeReflector.FullyTrusted)
contract.DefaultCreator = contract.GetUninitializedObject;
#endif
}
else if (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic)
{
ConstructorInfo constructor = GetParametrizedConstructor(contract.NonNullableUnderlyingType);
if (constructor != null)
{
#pragma warning disable 618
contract.ParametrizedConstructor = constructor;
#pragma warning restore 618
contract.CreatorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties));
}
}
}
@@ -703,8 +706,8 @@ namespace Newtonsoft.Json.Serialization
// then see whether object is compadible with any of the built in converters
contract.InternalConverter = JsonSerializer.GetMatchingConverter(BuiltInConverters, contract.NonNullableUnderlyingType);
if (ReflectionUtils.HasDefaultConstructor(contract.CreatedType, true)
|| contract.CreatedType.IsValueType())
if (contract.IsInstantiable
&& (ReflectionUtils.HasDefaultConstructor(contract.CreatedType, true) || contract.CreatedType.IsValueType()))
{
contract.DefaultCreator = GetDefaultCreator(contract.CreatedType);
@@ -86,12 +86,12 @@ namespace Newtonsoft.Json.Serialization
{
internal bool IsNullable;
internal bool IsConvertable;
internal bool IsSealed;
internal bool IsEnum;
internal Type NonNullableUnderlyingType;
internal ReadType InternalReadType;
internal JsonContractType ContractType;
internal bool IsReadOnlyOrFixedSize;
internal bool IsSealed;
internal bool IsInstantiable;
private List<SerializationCallback> _onDeserializedCallbacks;
@@ -99,6 +99,7 @@ namespace Newtonsoft.Json.Serialization
private IList<SerializationCallback> _onSerializedCallbacks;
private IList<SerializationCallback> _onSerializingCallbacks;
private IList<SerializationErrorCallback> _onErrorCallbacks;
private Type _createdType;
/// <summary>
/// Gets the underlying type for the contract.
@@ -110,7 +111,17 @@ namespace Newtonsoft.Json.Serialization
/// Gets or sets the type created during deserialization.
/// </summary>
/// <value>The type created during deserialization.</value>
public Type CreatedType { get; set; }
public Type CreatedType
{
get { return _createdType; }
set
{
_createdType = value;
IsSealed = _createdType.IsSealed();
IsInstantiable = !(_createdType.IsInterface() || _createdType.IsAbstract());
}
}
/// <summary>
/// Gets or sets whether this type contract is serialized as a reference.
@@ -296,9 +307,6 @@ namespace Newtonsoft.Json.Serialization
UnderlyingType = underlyingType;
IsSealed = underlyingType.IsSealed();
IsInstantiable = !(underlyingType.IsInterface() || underlyingType.IsAbstract());
IsNullable = ReflectionUtils.IsNullable(underlyingType);
NonNullableUnderlyingType = (IsNullable && ReflectionUtils.IsNullableType(underlyingType)) ? Nullable.GetUnderlyingType(underlyingType) : underlyingType;
@@ -1036,6 +1036,9 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
}
else
{
if (!contract.IsInstantiable)
throw JsonSerializationException.Create(reader, "Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
throw JsonSerializationException.Create(reader, "Unable to find a constructor to use for type {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
}
}
@@ -1064,6 +1067,9 @@ To fix this error either change the JSON to a {1} or change the deserialized typ
}
else
{
if (!contract.IsInstantiable)
throw JsonSerializationException.Create(reader, "Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
throw JsonSerializationException.Create(reader, "Unable to find a default constructor to use for type {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
}
}
@@ -1825,9 +1831,6 @@ To fix this error either change the environment to be fully trusted, change the
{
object newObject = null;
if (!objectContract.IsInstantiable)
throw JsonSerializationException.Create(reader, "Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, objectContract.UnderlyingType));
if (objectContract.OverrideCreator != null)
{
if (objectContract.CreatorParameters.Count > 0)
@@ -1854,7 +1857,12 @@ To fix this error either change the environment to be fully trusted, change the
}
if (newObject == null)
{
if (!objectContract.IsInstantiable)
throw JsonSerializationException.Create(reader, "Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, objectContract.UnderlyingType));
throw JsonSerializationException.Create(reader, "Unable to find a constructor to use for type {0}. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.".FormatWith(CultureInfo.InvariantCulture, objectContract.UnderlyingType));
}
createdFromNonDefaultCreator = false;
return newObject;