Skip to content
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
2 changes: 1 addition & 1 deletion src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@
<value>'JsonNumberHandlingAttribute' cannot be placed on a property, field, or type that is handled by a custom converter. See usage(s) of converter '{0}' on type '{1}'.</value>
</data>
<data name="NumberHandlingOnPropertyTypeMustBeNumberOrCollection" xml:space="preserve">
<value>When 'JsonNumberHandlingAttribute' is placed on a property or field, the property or field must be a number or a collection. See member '{0}' on type '{1}'.</value>
<value>When 'JsonNumberHandlingAttribute' is placed on a property or field, the property or field must be a number or a collection of numbers. See member '{0}' on type '{1}'.</value>
</data>
<data name="ConverterCanConvertNullableRedundant" xml:space="preserve">
<value>The converter '{0}' handles type '{1}' but is being asked to convert type '{2}'. Either create a separate converter for type '{2}' or change the converter's 'CanConvert' method to only return 'true' for a single type.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace System.Text.Json.Serialization.Converters
{
internal sealed class ObjectConverter : JsonConverter<object>
{
public ObjectConverter()
{
IsInternalConverterForNumberType = true;
}

public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
Expand Down Expand Up @@ -37,5 +42,13 @@ private JsonConverter GetRuntimeConverter(Type runtimeType, JsonSerializerOption

return runtimeConverter;
}

internal override object ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling handling)
{
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
{
return document.RootElement.Clone();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ internal bool TryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSeriali
// For performance, only perform validation on internal converters on debug builds.
if (IsInternalConverter)
{
if (IsInternalConverterForNumberType && state.Current.NumberHandling != null)
if (state.Current.NumberHandling != null)
{
value = ReadNumberWithCustomHandling(ref reader, state.Current.NumberHandling.Value);
}
Expand All @@ -179,7 +179,7 @@ internal bool TryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSeriali
int originalPropertyDepth = reader.CurrentDepth;
long originalPropertyBytesConsumed = reader.BytesConsumed;

if (IsInternalConverterForNumberType && state.Current.NumberHandling != null)
if (state.Current.NumberHandling != null)
{
value = ReadNumberWithCustomHandling(ref reader, state.Current.NumberHandling.Value);
}
Expand Down Expand Up @@ -356,7 +356,7 @@ internal bool TryWrite(Utf8JsonWriter writer, in T value, JsonSerializerOptions

int originalPropertyDepth = writer.CurrentDepth;

if (IsInternalConverterForNumberType && state.Current.NumberHandling != null)
if (state.Current.NumberHandling != null && IsInternalConverterForNumberType)
{
WriteNumberWithCustomHandling(writer, value, state.Current.NumberHandling.Value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,52 +184,89 @@ private void DetermineIgnoreCondition(JsonIgnoreCondition? ignoreCondition, bool

private void DetermineNumberHandling(JsonNumberHandling? parentTypeNumberHandling)
{
bool numberHandlingIsApplicable = ConverterBase.IsInternalConverterForNumberType || TypeIsCollectionOfNumbersWithInternalConverter();

if (IsForClassInfo)
{
if (parentTypeNumberHandling != null && !ConverterBase.IsInternalConverter)
{
ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(this);
}

// Priority 1: Get handling from the type (parent type in this case is the type itself).
NumberHandling = parentTypeNumberHandling;

// Priority 2: Get handling from JsonSerializerOptions instance.
if (!NumberHandling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
if (numberHandlingIsApplicable)
{
NumberHandling = Options.NumberHandling;
// This logic is to honor JsonNumberHandlingAttribute placed on
// custom collections e.g. public class MyNumberList : List<int>.

// Priority 1: Get handling from the type (parent type in this case is the type itself).
NumberHandling = parentTypeNumberHandling;

// Priority 2: Get handling from JsonSerializerOptions instance.
if (!NumberHandling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
{
NumberHandling = Options.NumberHandling;
}
}
}
else
{
JsonNumberHandling? handling = null;
Debug.Assert(MemberInfo != null);

JsonNumberHandlingAttribute? attribute = GetAttribute<JsonNumberHandlingAttribute>(MemberInfo);
if (attribute != null && !numberHandlingIsApplicable)
{
ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(this);
}

// Priority 1: Get handling from attribute on property or field.
if (MemberInfo != null)
if (numberHandlingIsApplicable)
{
JsonNumberHandlingAttribute? attribute = GetAttribute<JsonNumberHandlingAttribute>(MemberInfo);
// Priority 1: Get handling from attribute on property or field.
JsonNumberHandling? handling = attribute?.Handling;

// Priority 2: Get handling from attribute on parent class type.
handling ??= parentTypeNumberHandling;

if (attribute != null &&
!ConverterBase.IsInternalConverterForNumberType &&
((ClassType.Enumerable | ClassType.Dictionary) & ClassType) == 0)
// Priority 3: Get handling from JsonSerializerOptions instance.
if (!handling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
{
ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(this);
handling = Options.NumberHandling;
}

handling = attribute?.Handling;
NumberHandling = handling;
}
}
}

// Priority 2: Get handling from attribute on parent class type.
handling ??= parentTypeNumberHandling;

// Priority 3: Get handling from JsonSerializerOptions instance.
if (!handling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
{
handling = Options.NumberHandling;
}
private bool TypeIsCollectionOfNumbersWithInternalConverter()
{
if (!ConverterBase.IsInternalConverter ||
((ClassType.Enumerable | ClassType.Dictionary) & ClassType) == 0)
{
return false;
}

NumberHandling = handling;
Type? elementType = ConverterBase.ElementType;
Debug.Assert(elementType != null);

elementType = Nullable.GetUnderlyingType(elementType) ?? elementType;

if (elementType == typeof(byte) ||
elementType == typeof(decimal) ||
elementType == typeof(double) ||
elementType == typeof(short) ||
elementType == typeof(int) ||
elementType == typeof(long) ||
elementType == typeof(sbyte) ||
elementType == typeof(float) ||
elementType == typeof(ushort) ||
elementType == typeof(uint) ||
elementType == typeof(ulong) ||
elementType == JsonClassInfo.ObjectType)
{
return true;
}

return false;
}

public static TAttribute? GetAttribute<TAttribute>(MemberInfo memberInfo) where TAttribute : Attribute
Expand Down
Loading