Skip to content
Closed
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 @@ -545,6 +545,12 @@
<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>
</data>
<data name="DeserializeUnableToAssignValue" xml:space="preserve">
<value>The converted value of type '{0}' could not be assigned to the property or field '{1}' of type '{2}'.</value>
</data>
<data name="DeserializeUnableToAssignNull" xml:space="preserve">
<value>The converted value 'null' could not be assigned to the property or field '{0}' of type '{1}'.</value>
</data>
<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>
Expand Down
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 @@ -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 @@ -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 @@ -222,15 +222,17 @@ private static DynamicMethod CreateImmutableDictionaryCreateRangeDelegate(Type c
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,87 @@ private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Typ
generator.Emit(OpCodes.Callvirt, realMethod);
}

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

if (declaredPropertyType.IsValueType && declaredPropertyType != runtimePropertyType)
{
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) =>
CreatePropertySetterDelegate<TProperty>(propertyInfo);

private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Type propertyType)
private static Action<object, TProperty> CreatePropertySetterDelegate<TProperty>(PropertyInfo propertyInfo)
{
string memberName = propertyInfo.Name;
Type declaredPropertyType = propertyInfo.PropertyType;
Type runtimePropertyType = typeof(TProperty);

Action<object, TProperty> propertySetter = CreateDelegate<Action<object, TProperty>>(
CreatePropertySetter(propertyInfo, runtimePropertyType));

if (declaredPropertyType == runtimePropertyType)
{
// If declared and runtime type are identical, the assignment cannot fail, thus
// there is no need for exception handling or closures.

return propertySetter;
}
else
{
// Return a delegate with closures and exception handling.

return (object obj, TProperty value) =>
{
try
{
propertySetter(obj, value);
}
catch (NullReferenceException)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToAssignNull(memberName, declaredPropertyType);
}
catch (InvalidCastException)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToAssignValue(value!.GetType(), memberName, declaredPropertyType);
}
};
}
}

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)
{
generator.Emit(OpCodes.Unbox, declaringType);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, realMethod);
// When applied to a reference type, the unbox.any instruction has the same effect as castclass.
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,44 +337,97 @@ 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);
generator.Emit(
declaringType.IsValueType
? OpCodes.Unbox
: OpCodes.Castclass,
declaringType);
generator.Emit(declaringType.IsValueType ? OpCodes.Unbox : 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;
}

public override Action<object, TProperty> CreateFieldSetter<TProperty>(FieldInfo fieldInfo) =>
CreateDelegate<Action<object, TProperty>>(CreateFieldSetter(fieldInfo, typeof(TProperty)));
CreateFieldSetterDelegate<TProperty>(fieldInfo);

private static Action<object, TProperty> CreateFieldSetterDelegate<TProperty>(FieldInfo fieldInfo)
{
string memberName = fieldInfo.Name;
Type declaredFieldType = fieldInfo.FieldType;
Type runtimeFieldType = typeof(TProperty);

Action<object, TProperty> fieldSetter = CreateDelegate<Action<object, TProperty>>(
CreateFieldSetter(fieldInfo, runtimeFieldType));

if (declaredFieldType == runtimeFieldType)
{
// If declared and runtime type are identical, the assignment cannot fail, thus
// there is no need for exception handling or closures.

return fieldSetter;
}
else
{
// Return a delegate with closures and exception handling.

return (object obj, TProperty value) =>
{
try
{
fieldSetter(obj, value);
}
catch (NullReferenceException)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToAssignNull(memberName, declaredFieldType);
}
catch (InvalidCastException)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToAssignValue(value!.GetType(), memberName, declaredFieldType);
}
};
}
}

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)
{
// When applied to a reference type, the unbox.any instruction has the same effect as castclass.
generator.Emit(OpCodes.Unbox_Any, declaredFieldType);
}

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

Expand Down
Loading