-Added improved support for deserializing immutable structs

This commit is contained in:
James Newton-King
2017-02-26 19:11:45 +13:00
parent a49773e2b2
commit b5f1b83191
2 changed files with 102 additions and 9 deletions
@@ -96,6 +96,48 @@ namespace Newtonsoft.Json.Tests.Serialization
[TestFixture]
public class JsonSerializerTest : TestFixtureBase
{
public struct ImmutableStruct
{
public ImmutableStruct(string value)
{
Value = value;
Value2 = 0;
}
public string Value { get; }
public int Value2 { get; set; }
}
[Test]
public void DeserializeImmutableStruct()
{
var result = JsonConvert.DeserializeObject<ImmutableStruct>("{ \"Value\": \"working\", \"Value2\": 2 }");
Assert.AreEqual("working", result.Value);
Assert.AreEqual(2, result.Value2);
}
public struct AlmostImmutableStruct
{
public AlmostImmutableStruct(string value, int value2)
{
Value = value;
Value2 = value2;
}
public string Value { get; }
public int Value2 { get; set; }
}
[Test]
public void DeserializeAlmostImmutableStruct()
{
var result = JsonConvert.DeserializeObject<AlmostImmutableStruct>("{ \"Value\": \"working\", \"Value2\": 2 }");
Assert.AreEqual(null, result.Value);
Assert.AreEqual(2, result.Value2);
}
public class ErroringClass
{
public DateTime Tags { get; set; }
@@ -373,6 +373,17 @@ namespace Newtonsoft.Json.Serialization
contract.CreatorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties));
}
}
else if (contract.NonNullableUnderlyingType.IsValueType())
{
// value types always have default constructor
// check whether there is a constructor that matches with non-writable properties on value type
ConstructorInfo constructor = GetImmutableConstructor(contract.NonNullableUnderlyingType, contract.Properties);
if (constructor != null)
{
contract.OverrideCreator = JsonTypeReflector.ReflectionDelegateFactory.CreateParameterizedConstructor(constructor);
contract.CreatorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties));
}
}
}
MemberInfo extensionDataMember = GetExtensionDataMemberForType(contract.NonNullableUnderlyingType);
@@ -566,6 +577,35 @@ namespace Newtonsoft.Json.Serialization
return null;
}
private ConstructorInfo GetImmutableConstructor(Type objectType, JsonPropertyCollection memberProperties)
{
IEnumerable<ConstructorInfo> constructors = objectType.GetConstructors();
IEnumerator<ConstructorInfo> en = constructors.GetEnumerator();
if (en.MoveNext())
{
ConstructorInfo constructor = en.Current;
if (!en.MoveNext())
{
ParameterInfo[] parameters = constructor.GetParameters();
if (parameters.Length > 0)
{
foreach (ParameterInfo parameterInfo in parameters)
{
var memberProperty = MatchProperty(memberProperties, parameterInfo.Name, parameterInfo.ParameterType);
if (memberProperty == null || memberProperty.Writable)
{
return null;
}
}
return constructor;
}
}
}
return null;
}
private ConstructorInfo GetParameterizedConstructor(Type objectType)
{
#if PORTABLE
@@ -603,15 +643,7 @@ namespace Newtonsoft.Json.Serialization
foreach (ParameterInfo parameterInfo in constructorParameters)
{
// it is possible to generate a ParameterInfo with a null name using Reflection.Emit
// protect against an ArgumentNullException from GetClosestMatchProperty by testing for null here
JsonProperty matchingMemberProperty = (parameterInfo.Name != null) ? memberProperties.GetClosestMatchProperty(parameterInfo.Name) : null;
// type must match as well as name
if (matchingMemberProperty != null && matchingMemberProperty.PropertyType != parameterInfo.ParameterType)
{
matchingMemberProperty = null;
}
JsonProperty matchingMemberProperty = MatchProperty(memberProperties, parameterInfo.Name, parameterInfo.ParameterType);
// ensure that property will have a name from matching property or from parameterinfo
// parameterinfo could have no name if generated by a proxy (I'm looking at you Castle)
@@ -629,6 +661,25 @@ namespace Newtonsoft.Json.Serialization
return parameterCollection;
}
private JsonProperty MatchProperty(JsonPropertyCollection properties, string name, Type type)
{
// it is possible to generate a member with a null name using Reflection.Emit
// protect against an ArgumentNullException from GetClosestMatchProperty by testing for null here
if (name == null)
{
return null;
}
JsonProperty property = properties.GetClosestMatchProperty(name);
// must match type as well as name
if (property == null || property.PropertyType != type)
{
return null;
}
return property;
}
/// <summary>
/// Creates a <see cref="JsonProperty"/> for the given <see cref="ParameterInfo"/>.
/// </summary>