-Added improved support for deserializing immutable structs
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user