Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set default value for value-type ctor params properly #43831

Merged
merged 2 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}