Skip to content

Commit

Permalink
Set default value for value-type ctor params properly (#43831)
Browse files Browse the repository at this point in the history
* Set default value for value-type ctor params properly

* Address review feedback
  • Loading branch information
layomia committed Oct 30, 2020
1 parent 4d3ca5f commit c0b645d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Reflection;

namespace System.Text.Json
Expand All @@ -25,10 +26,19 @@ public override void Initialize(
matchingProperty,
options);

Debug.Assert(parameterInfo.ParameterType == matchingProperty.DeclaredPropertyType);

if (parameterInfo.HasDefaultValue)
{
DefaultValue = parameterInfo.DefaultValue;
TypedDefaultValue = (T)parameterInfo.DefaultValue!;
if (parameterInfo.DefaultValue == null && !matchingProperty.PropertyTypeCanBeNull)
{
DefaultValue = TypedDefaultValue;
}
else
{
DefaultValue = parameterInfo.DefaultValue;
TypedDefaultValue = (T)parameterInfo.DefaultValue!;
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,8 @@ public JsonClassInfo RuntimeClassInfo
public bool IsIgnored { get; private set; }

public JsonNumberHandling? NumberHandling { get; private set; }

// Whether the property type can be null.
public bool PropertyTypeCanBeNull { get; protected set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ internal sealed class JsonPropertyInfo<T> : JsonPropertyInfo
// Since a converter's TypeToConvert (which is the T value in this type) can be different than
// the property's type, we track that and whether the property type can be null.
private bool _propertyTypeEqualsTypeToConvert;
private bool _propertyTypeCanBeNull;

public Func<object, T>? Get { get; private set; }
public Action<object, T>? Set { get; private set; }
Expand Down Expand Up @@ -105,10 +104,10 @@ public override void Initialize(
}

_converterIsExternalAndPolymorphic = !converter.IsInternalConverter && DeclaredPropertyType != converter.TypeToConvert;
_propertyTypeCanBeNull = DeclaredPropertyType.CanBeNull();
PropertyTypeCanBeNull = DeclaredPropertyType.CanBeNull();
_propertyTypeEqualsTypeToConvert = typeof(T) == DeclaredPropertyType;

GetPolicies(ignoreCondition, parentTypeNumberHandling, defaultValueIsNull: _propertyTypeCanBeNull);
GetPolicies(ignoreCondition, parentTypeNumberHandling, defaultValueIsNull: PropertyTypeCanBeNull);
}

public override JsonConverter ConverterBase
Expand Down Expand Up @@ -147,7 +146,7 @@ public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf
return true;
}

if (!_propertyTypeCanBeNull)
if (!PropertyTypeCanBeNull)
{
if (_propertyTypeEqualsTypeToConvert)
{
Expand All @@ -172,7 +171,7 @@ public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf

if (value == null)
{
Debug.Assert(_propertyTypeCanBeNull);
Debug.Assert(PropertyTypeCanBeNull);

if (Converter.HandleNullOnWrite)
{
Expand Down Expand Up @@ -235,7 +234,7 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U
bool isNullToken = reader.TokenType == JsonTokenType.Null;
if (isNullToken && !Converter.HandleNullOnRead && !state.IsContinuation)
{
if (!_propertyTypeCanBeNull)
if (!PropertyTypeCanBeNull)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Converter.TypeToConvert);
}
Expand All @@ -255,7 +254,7 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U
// CanUseDirectReadOrWrite == false when using streams
Debug.Assert(!state.IsContinuation);

if (!isNullToken || !IgnoreDefaultValuesOnRead || !_propertyTypeCanBeNull)
if (!isNullToken || !IgnoreDefaultValuesOnRead || !PropertyTypeCanBeNull)
{
// Optimize for internal converters by avoiding the extra call to TryRead.
T fastValue = Converter.Read(ref reader, RuntimePropertyType!, Options);
Expand All @@ -267,7 +266,7 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U
else
{
success = true;
if (!isNullToken || !IgnoreDefaultValuesOnRead || !_propertyTypeCanBeNull || state.IsContinuation)
if (!isNullToken || !IgnoreDefaultValuesOnRead || !PropertyTypeCanBeNull || state.IsContinuation)
{
success = Converter.TryRead(ref reader, RuntimePropertyType!, Options, ref state, out T value);
if (success)
Expand All @@ -284,7 +283,7 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U
ThrowHelper.ThrowInvalidCastException_DeserializeUnableToAssignValue(typeOfValue, DeclaredPropertyType);
}
}
else if (!_propertyTypeCanBeNull)
else if (!PropertyTypeCanBeNull)
{
ThrowHelper.ThrowInvalidOperationException_DeserializeUnableToAssignNull(DeclaredPropertyType);
}
Expand All @@ -304,7 +303,7 @@ public override bool ReadJsonAsObject(ref ReadStack state, ref Utf8JsonReader re
bool isNullToken = reader.TokenType == JsonTokenType.Null;
if (isNullToken && !Converter.HandleNullOnRead && !state.IsContinuation)
{
if (!_propertyTypeCanBeNull)
if (!PropertyTypeCanBeNull)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Converter.TypeToConvert);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1139,5 +1139,57 @@ public void PocoWithSamePropertyNameDifferentTypes()
AgePoco obj = JsonSerializer.Deserialize<AgePoco>(@"{""age"":1}");
Assert.Equal(1, obj.age);
}

[Theory]
[InlineData(typeof(TypeWithGuid))]
[InlineData(typeof(TypeWithNullableGuid))]
public void DefaultForValueTypeCtorParam(Type type)
{
string json = @"{""MyGuid"":""edc421bf-782a-4a95-ad67-3d73b5d7db6f""}";
object obj = JsonSerializer.Deserialize(json, type);
Assert.Equal(json, JsonSerializer.Serialize(obj));

json = @"{}";
obj = JsonSerializer.Deserialize(json, type);

var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault };
Assert.Equal(json, JsonSerializer.Serialize(obj, options));
}

private class TypeWithGuid
{
public Guid MyGuid { get; }

public TypeWithGuid(Guid myGuid = default) => MyGuid = myGuid;
}

private struct TypeWithNullableGuid
{
public Guid? MyGuid { get; }

[JsonConstructor]
public TypeWithNullableGuid(Guid? myGuid = default) => MyGuid = myGuid;
}

[Fact]
public void DefaultForReferenceTypeCtorParam()
{
string json = @"{""MyUri"":""http://hello""}";
object obj = JsonSerializer.Deserialize(json, typeof(TypeWithUri));
Assert.Equal(json, JsonSerializer.Serialize(obj));

json = @"{}";
obj = JsonSerializer.Deserialize(json, typeof(TypeWithUri));

var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault };
Assert.Equal(json, JsonSerializer.Serialize(obj, options));
}

private class TypeWithUri
{
public Uri MyUri { get; }

public TypeWithUri(Uri myUri = default) => MyUri = myUri;
}
}
}

0 comments on commit c0b645d

Please sign in to comment.