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
6 changes: 6 additions & 0 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -548,4 +548,10 @@
<data name="MetadataReferenceOfTypeCannotBeAssignedToType" xml:space="preserve">
<value>The object with reference id '{0}' of type '{1}' cannot be assigned to the type '{2}'.</value>
</data>
<data name="DeserializeUnableToAssignValue" xml:space="preserve">
<value>Unable to cast object of type '{0}' to type '{1}'.</value>
</data>
<data name="DeserializeUnableToAssignNull" xml:space="preserve">
<value>Unable to assign 'null' to the property or field of type '{0}'.</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
<Compile Include="System\Text\Json\Serialization\WriteStackFrame.cs" />
<Compile Include="System\Text\Json\ThrowHelper.cs" />
<Compile Include="System\Text\Json\ThrowHelper.Serialization.cs" />
<Compile Include="System\Text\Json\TypeExtensions.cs" />
<Compile Include="System\Text\Json\Writer\JsonWriterHelper.cs" />
<Compile Include="System\Text\Json\Writer\JsonWriterHelper.Date.cs" />
<Compile Include="System\Text\Json\Writer\JsonWriterHelper.Escaping.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer
JsonConverter valueConverter = options.GetConverter(valueTypeToConvert);
Debug.Assert(valueConverter != null);

// If the value type has an interface or object converter, just return that converter directly.
if (!valueConverter.TypeToConvert.IsValueType && valueTypeToConvert.IsValueType)
{
return valueConverter;
}

return CreateValueConverter(valueTypeToConvert, valueConverter);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,10 @@ internal bool TryWrite(Utf8JsonWriter writer, in T value, JsonSerializerOptions
return true;
}

if (type != TypeToConvert)
if (type != TypeToConvert && IsInternalConverter)
{
// Handle polymorphic case and get the new converter.
// For internal converter only: Handle polymorphic case and get the new converter.
// Custom converter, even though polymorphic converter, get called for reading AND writing.
JsonConverter jsonConverter = state.Current.InitializeReEntry(type, options);
if (jsonConverter != this)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,21 @@ public override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref U
success = Converter.TryRead(ref reader, RuntimePropertyType!, Options, ref state, out T value);
if (success)
{
if (!Converter.IsInternalConverter)
{
if (value != null)
{
Type typeOfValue = value.GetType();
if (!DeclaredPropertyType.IsAssignableFrom(typeOfValue))
{
ThrowHelper.ThrowInvalidCastException_DeserializeUnableToAssignValue(typeOfValue, DeclaredPropertyType);
}
}
else if (DeclaredPropertyType.IsValueType && !DeclaredPropertyType.IsNullableValueType())
{
ThrowHelper.ThrowInvalidOperationException_DeserializeUnableToAssignNull(DeclaredPropertyType);
}
}
Set!(obj, value!);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ public sealed partial class JsonSerializerOptions
// The global list of built-in simple converters.
private static readonly Dictionary<Type, JsonConverter> s_defaultSimpleConverters = GetDefaultSimpleConverters();

private static readonly Type s_nullableOfTType = typeof(Nullable<>);

// The global list of built-in converters that override CanConvert().
private static readonly JsonConverter[] s_defaultFactoryConverters = new JsonConverter[]
{
Expand Down Expand Up @@ -186,7 +184,7 @@ internal JsonConverter DetermineConverter(Type? parentClassType, Type runtimePro
// We also throw to avoid passing an invalid argument to setters for nullable struct properties,
// which would cause an InvalidProgramException when the generated IL is invoked.
// This is not an issue of the converter is wrapped in NullableConverter<T>.
if (IsNullableType(runtimePropertyType) && !IsNullableType(converter.TypeToConvert))
if (runtimePropertyType.IsNullableType() && !converter.TypeToConvert.IsNullableType())
{
ThrowHelper.ThrowInvalidOperationException_ConverterCanConvertNullableRedundant(runtimePropertyType, converter);
}
Expand Down Expand Up @@ -274,8 +272,8 @@ public JsonConverter GetConverter(Type typeToConvert)

Type converterTypeToConvert = converter.TypeToConvert;

if (!converterTypeToConvert.IsAssignableFrom(typeToConvert) &&
!typeToConvert.IsAssignableFrom(converterTypeToConvert))
if (!converterTypeToConvert.IsAssignableFromInternal(typeToConvert)
&& !typeToConvert.IsAssignableFromInternal(converterTypeToConvert))
{
ThrowHelper.ThrowInvalidOperationException_SerializationConverterNotCompatible(converter.GetType(), typeToConvert);
}
Expand Down Expand Up @@ -366,9 +364,5 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter
return default;
}

private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == s_nullableOfTType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,18 +219,20 @@ private static DynamicMethod CreateImmutableDictionaryCreateRangeDelegate(Type c
return dynamicMethod;
}

public override Func<object?, TProperty> CreatePropertyGetter<TProperty>(PropertyInfo propertyInfo) =>
CreateDelegate<Func<object?, TProperty>>(CreatePropertyGetter(propertyInfo, typeof(TProperty)));
public override Func<object, TProperty> CreatePropertyGetter<TProperty>(PropertyInfo propertyInfo) =>
CreateDelegate<Func<object, TProperty>>(CreatePropertyGetter(propertyInfo, typeof(TProperty)));

private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Type propertyType)
private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Type runtimePropertyType)
{
MethodInfo? realMethod = propertyInfo.GetMethod;
Debug.Assert(realMethod != null);

Type? declaringType = propertyInfo.DeclaringType;
Debug.Assert(declaringType != null);

DynamicMethod dynamicMethod = CreateGetterMethod(propertyInfo.Name, propertyType);
Type declaredPropertyType = propertyInfo.PropertyType;

DynamicMethod dynamicMethod = CreateGetterMethod(propertyInfo.Name, runtimePropertyType);
ILGenerator generator = dynamicMethod.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0);
Expand All @@ -246,40 +248,48 @@ private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Typ
generator.Emit(OpCodes.Callvirt, realMethod);
}

// declaredPropertyType: Type of the property
// runtimePropertyType: <T> of JsonConverter / JsonPropertyInfo

if (declaredPropertyType != runtimePropertyType && declaredPropertyType.IsValueType)
{
generator.Emit(OpCodes.Box, declaredPropertyType);
}

generator.Emit(OpCodes.Ret);

return dynamicMethod;
}

public override Action<object?, TProperty> CreatePropertySetter<TProperty>(PropertyInfo propertyInfo) =>
CreateDelegate<Action<object?, TProperty>>(CreatePropertySetter(propertyInfo, typeof(TProperty)));
public override Action<object, TProperty> CreatePropertySetter<TProperty>(PropertyInfo propertyInfo) =>
CreateDelegate<Action<object, TProperty>>(CreatePropertySetter(propertyInfo, typeof(TProperty)));

private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Type propertyType)
private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Type runtimePropertyType)
{
MethodInfo? realMethod = propertyInfo.SetMethod;
Debug.Assert(realMethod != null);

Type? declaringType = propertyInfo.DeclaringType;
Debug.Assert(declaringType != null);

DynamicMethod dynamicMethod = CreateSetterMethod(propertyInfo.Name, propertyType);
Type declaredPropertyType = propertyInfo.PropertyType;

DynamicMethod dynamicMethod = CreateSetterMethod(propertyInfo.Name, runtimePropertyType);
ILGenerator generator = dynamicMethod.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0);
generator.Emit(declaringType.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, declaringType);
generator.Emit(OpCodes.Ldarg_1);

if (declaringType.IsValueType)
// declaredPropertyType: Type of the property
// runtimePropertyType: <T> of JsonConverter / JsonPropertyInfo

if (declaredPropertyType != runtimePropertyType && declaredPropertyType.IsValueType)
{
generator.Emit(OpCodes.Unbox, declaringType);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, realMethod);
generator.Emit(OpCodes.Unbox_Any, declaredPropertyType);
}
else
{
generator.Emit(OpCodes.Castclass, declaringType);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, realMethod);
};

generator.Emit(declaringType.IsValueType ? OpCodes.Call : OpCodes.Callvirt, realMethod);
generator.Emit(OpCodes.Ret);

return dynamicMethod;
Expand All @@ -288,12 +298,14 @@ private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Typ
public override Func<object, TProperty> CreateFieldGetter<TProperty>(FieldInfo fieldInfo) =>
CreateDelegate<Func<object, TProperty>>(CreateFieldGetter(fieldInfo, typeof(TProperty)));

private static DynamicMethod CreateFieldGetter(FieldInfo fieldInfo, Type fieldType)
private static DynamicMethod CreateFieldGetter(FieldInfo fieldInfo, Type runtimeFieldType)
{
Type? declaringType = fieldInfo.DeclaringType;
Debug.Assert(declaringType != null);

DynamicMethod dynamicMethod = CreateGetterMethod(fieldInfo.Name, fieldType);
Type declaredFieldType = fieldInfo.FieldType;

DynamicMethod dynamicMethod = CreateGetterMethod(fieldInfo.Name, runtimeFieldType);
ILGenerator generator = dynamicMethod.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0);
Expand All @@ -303,6 +315,15 @@ private static DynamicMethod CreateFieldGetter(FieldInfo fieldInfo, Type fieldTy
: OpCodes.Castclass,
declaringType);
generator.Emit(OpCodes.Ldfld, fieldInfo);

// declaredFieldType: Type of the field
// runtimeFieldType: <T> of JsonConverter / JsonPropertyInfo

if (declaredFieldType.IsValueType && declaredFieldType != runtimeFieldType)
{
generator.Emit(OpCodes.Box, declaredFieldType);
}

generator.Emit(OpCodes.Ret);

return dynamicMethod;
Expand All @@ -311,21 +332,28 @@ private static DynamicMethod CreateFieldGetter(FieldInfo fieldInfo, Type fieldTy
public override Action<object, TProperty> CreateFieldSetter<TProperty>(FieldInfo fieldInfo) =>
CreateDelegate<Action<object, TProperty>>(CreateFieldSetter(fieldInfo, typeof(TProperty)));

private static DynamicMethod CreateFieldSetter(FieldInfo fieldInfo, Type fieldType)
private static DynamicMethod CreateFieldSetter(FieldInfo fieldInfo, Type runtimeFieldType)
{
Type? declaringType = fieldInfo.DeclaringType;
Debug.Assert(declaringType != null);

DynamicMethod dynamicMethod = CreateSetterMethod(fieldInfo.Name, fieldType);
Type declaredFieldType = fieldInfo.FieldType;

DynamicMethod dynamicMethod = CreateSetterMethod(fieldInfo.Name, runtimeFieldType);
ILGenerator generator = dynamicMethod.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0);
generator.Emit(
declaringType.IsValueType
? OpCodes.Unbox
: OpCodes.Castclass,
declaringType);
generator.Emit(declaringType.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, declaringType);
generator.Emit(OpCodes.Ldarg_1);

// declaredFieldType: Type of the field
// runtimeFieldType: <T> of JsonConverter / JsonPropertyInfo

if (declaredFieldType != runtimeFieldType && declaredFieldType.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, declaredFieldType);
}

generator.Emit(OpCodes.Stfld, fieldInfo);
generator.Emit(OpCodes.Ret);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ public static void ThrowJsonException_DeserializeUnableToConvertValue(Type prope
throw ex;
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidCastException_DeserializeUnableToAssignValue(Type typeOfValue, Type declaredType)
{
throw new InvalidCastException(SR.Format(SR.DeserializeUnableToAssignValue, typeOfValue, declaredType));
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidOperationException_DeserializeUnableToAssignNull(Type declaredType)
{
throw new InvalidOperationException(SR.Format(SR.DeserializeUnableToAssignNull, declaredType));
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowJsonException_SerializationConverterRead(JsonConverter? converter)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace System.Text.Json
{
internal static class TypeExtensions
{
/// <summary>
/// Returns <see langword="true" /> when the given type is of type <see cref="Nullable{T}"/>.
/// </summary>
public static bool IsNullableValueType(this Type type)
{
return Nullable.GetUnderlyingType(type) != null;
}

/// <summary>
/// Returns <see langword="true" /> when the given type is either a reference type or of type <see cref="Nullable{T}"/>.
/// </summary>
public static bool IsNullableType(this Type type)
{
return !type.IsValueType || IsNullableValueType(type);
}

/// <summary>
/// Returns <see langword="true" /> when the given type is assignable from <paramref name="from"/>.
/// </summary>
/// <remarks>
/// Other than <see cref="Type.IsAssignableFrom(Type)"/> also returns <see langword="true" /> when <paramref name="type"/> is of type <see cref="Nullable{T}"/> where <see langword="T" /> : <see langword="IInterface" /> and <paramref name="from"/> is of type <see langword="IInterface" />.
/// </remarks>
public static bool IsAssignableFromInternal(this Type type, Type from)
{
if (IsNullableValueType(from) && type.IsInterface)
{
return type.IsAssignableFrom(from.GetGenericArguments()[0]);
}

return type.IsAssignableFrom(from);
}
}
}
20 changes: 20 additions & 0 deletions src/libraries/System.Text.Json/tests/AssertHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Xunit;

namespace System.Text.Json.Tests
{
public static class AssertHelper
{
public static void ValidateJson(IEnumerable<string> expectedProperties, string json)
{
Assert.StartsWith("{", json);
Assert.EndsWith("}", json);
foreach (string expectedProperty in expectedProperties)
Assert.Contains(expectedProperty, json);
}

}
}
2 changes: 1 addition & 1 deletion src/libraries/System.Text.Json/tests/JsonPropertyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private static void AssertContents(string expectedValue, ArrayBufferWriter<byte>
[InlineData(null)]
public static void NameEquals_InvalidInstance_Throws(string text)
{
const string ErrorMessage = "Operation is not valid due to the current state of the object.";
string ErrorMessage = new InvalidOperationException().Message;
JsonProperty prop = default;
AssertExtensions.Throws<InvalidOperationException>(() => prop.NameEquals(text), ErrorMessage);
AssertExtensions.Throws<InvalidOperationException>(() => prop.NameEquals(text.AsSpan()), ErrorMessage);
Expand Down
Loading