From 2e68cbf24ab0c86f42caf31e86b0edf3b369f896 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 2 Aug 2022 19:55:55 +0100 Subject: [PATCH 1/3] Fix InvalidCastException when casting a polymorphic converter. --- .../Collection/JsonDictionaryConverter.cs | 5 +- .../CustomConverterTests.Polymorphic.cs | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs index 78660b8f6aa8c..21a2163a2747b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs @@ -70,10 +70,7 @@ protected virtual void CreateCollection(ref Utf8JsonReader reader, ref ReadStack protected static JsonConverter GetConverter(JsonTypeInfo typeInfo) { - JsonConverter converter = (JsonConverter)typeInfo.Converter; - Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. - - return converter; + return ((JsonTypeInfo)typeInfo).EffectiveConverter; } internal sealed override bool OnTryRead( diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs index 1aecc2a7bd314..e62d33b1dd14d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs @@ -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.Collections.Generic; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -215,5 +216,59 @@ public static void PersonConverterSerializerPolymorphic() Assert.Equal("C", customers[0].Name); } } + + [Fact] + public static void PolymorphicConverter_ShouldWorkInAllContexts() + { + // Regression test for https://github.com/dotnet/runtime/issues/46522 + + var value = new SampleRepro(); + string expectedJson = "\"string\""; + + string json = JsonSerializer.Serialize(value); + Assert.Equal(expectedJson, json); + + json = JsonSerializer.Serialize(new { Value = value }); + Assert.Equal($@"{{""Value"":{expectedJson}}}", json); + + json = JsonSerializer.Serialize(new[] { value }); + Assert.Equal($"[{expectedJson}]", json); + + json = JsonSerializer.Serialize(new Dictionary { ["key"] = value }); + Assert.Equal($@"{{""key"":{expectedJson}}}", json); + } + + public interface IRepro + { + T Value { get; } + } + + [JsonConverter(typeof(ReproJsonConverter))] + public class SampleRepro : IRepro + { + public object Value => "string"; + } + + public sealed class ReproJsonConverter : JsonConverter> + { + public override bool CanConvert(Type typeToConvert) + { + return typeof(IRepro).IsAssignableFrom(typeToConvert); + } + + public override IRepro Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotSupportedException(); + + public override void Write(Utf8JsonWriter writer, IRepro value, JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, value.Value, options); + } + } + } } } From bc0f644882ac741b4687be2916029a942b5415af Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 3 Aug 2022 17:45:46 +0100 Subject: [PATCH 2/3] Extend test coverage and fixes for the case of deserialization. --- .../Converters/CastingConverter.cs | 8 +++ .../Collection/JsonCollectionConverter.cs | 11 +--- .../Json/Serialization/JsonConverterOfT.cs | 32 ++++++---- .../Metadata/JsonPropertyInfo.cs | 38 ++++++------ .../Metadata/JsonPropertyInfoOfT.cs | 59 +++++++++++-------- .../Serialization/Metadata/JsonTypeInfoOfT.cs | 7 ++- .../CustomConverterTests.Polymorphic.cs | 36 ++++++----- 7 files changed, 115 insertions(+), 76 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs index 0242cb2ab30ad..960fa77b1faf8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs @@ -1,6 +1,9 @@ // 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.Text.Json.Reflection; + namespace System.Text.Json.Serialization.Converters { /// @@ -18,6 +21,9 @@ internal sealed class CastingConverter : JsonConverter internal CastingConverter(JsonConverter sourceConverter) : base(initialize: false) { + Debug.Assert(typeof(T).IsInSubtypeRelationshipWith(typeof(TSource))); + Debug.Assert(sourceConverter.SourceConverterForCastingConverter is null, "casting converters should not be layered."); + _sourceConverter = sourceConverter; Initialize(); @@ -28,6 +34,8 @@ internal CastingConverter(JsonConverter sourceConverter) : base(initial CanBePolymorphic = sourceConverter.CanBePolymorphic; } + internal override JsonConverter? SourceConverterForCastingConverter => _sourceConverter; + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => CastOnRead(_sourceConverter.Read(ref reader, typeToConvert, options)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs index 2ee506e720496..485189e69bb33 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs @@ -49,18 +49,13 @@ protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOpti protected static JsonConverter GetElementConverter(JsonTypeInfo elementTypeInfo) { - JsonConverter converter = (JsonConverter)elementTypeInfo.Converter; - Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. - - return converter; + return ((JsonTypeInfo)elementTypeInfo).EffectiveConverter; } protected static JsonConverter GetElementConverter(ref WriteStack state) { - JsonConverter converter = (JsonConverter)state.Current.JsonPropertyInfo!.EffectiveConverter; - Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. - - return converter; + Debug.Assert(state.Current.JsonPropertyInfo != null); + return (JsonConverter)state.Current.JsonPropertyInfo.EffectiveConverter; } internal override bool OnTryRead( diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs index 02021bdc8eb9f..1eae8cbf80e7b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs @@ -16,26 +16,24 @@ public abstract partial class JsonConverter : JsonConverter /// /// When overridden, constructs a new instance. /// - protected internal JsonConverter() - { - Initialize(); - } + protected internal JsonConverter() : this(initialize: true) + { } internal JsonConverter(bool initialize) { + IsValueType = typeof(T).IsValueType; + IsInternalConverter = GetType().Assembly == typeof(JsonConverter).Assembly; + // Initialize uses abstract members, in order for them to be initialized correctly - // without throwing we need to delay call to Initialize + // without throwing we might need to delay call to Initialize if (initialize) { Initialize(); } } - internal void Initialize() + private protected void Initialize() { - IsValueType = typeof(T).IsValueType; - IsInternalConverter = GetType().Assembly == typeof(JsonConverter).Assembly; - if (HandleNull) { HandleNullOnRead = true; @@ -76,10 +74,24 @@ internal sealed override JsonParameterInfo CreateJsonParameterInfo() internal sealed override JsonConverter CreateCastingConverter() { + if (this is JsonConverter conv) + { + return conv; + } + JsonSerializerOptions.CheckConverterNullabilityIsSameAsPropertyType(this, typeof(TTarget)); - return new CastingConverter(this); + + // Avoid layering casting converters by consulting any source converters directly. + return + SourceConverterForCastingConverter?.CreateCastingConverter() + ?? new CastingConverter(this); } + /// + /// Set if this converter is itself a casting converter. + /// + internal virtual JsonConverter? SourceConverterForCastingConverter => null; + internal override Type? KeyType => null; internal override Type? ElementType => null; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 9a49f91cff5ac..137a3b238bace 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -25,7 +25,16 @@ public abstract class JsonPropertyInfo /// /// Converter after applying CustomConverter (i.e. JsonConverterAttribute) /// - internal abstract JsonConverter EffectiveConverter { get; } + internal JsonConverter EffectiveConverter + { + get + { + Debug.Assert(_effectiveConverter != null); + return _effectiveConverter; + } + } + + private protected JsonConverter? _effectiveConverter; /// /// Gets or sets a custom converter override for the current property. @@ -705,16 +714,15 @@ internal bool ReadJsonAndAddExtensionProperty( } else { - JsonConverter converter = (JsonConverter)GetDictionaryValueConverter(JsonTypeInfo.ObjectType); + JsonConverter converter = GetDictionaryValueConverter(); object value = converter.Read(ref reader, JsonTypeInfo.ObjectType, Options)!; dictionaryObjectValue[state.Current.JsonPropertyNameAsString!] = value; } } else if (propValue is IDictionary dictionaryElementValue) { - Type elementType = typeof(JsonElement); - JsonConverter converter = (JsonConverter)GetDictionaryValueConverter(elementType); - JsonElement value = converter.Read(ref reader, elementType, Options); + JsonConverter converter = GetDictionaryValueConverter(); + JsonElement value = converter.Read(ref reader, typeof(JsonElement), Options); dictionaryElementValue[state.Current.JsonPropertyNameAsString!] = value; } else @@ -726,25 +734,17 @@ internal bool ReadJsonAndAddExtensionProperty( return true; - JsonConverter GetDictionaryValueConverter(Type dictionaryValueType) + JsonConverter GetDictionaryValueConverter() { - JsonConverter converter; - JsonTypeInfo? dictionaryValueInfo = JsonTypeInfo.ElementTypeInfo; - if (dictionaryValueInfo != null) - { - // Fast path when there is a generic type such as Dictionary<,>. - converter = dictionaryValueInfo.Converter; - } - else - { + JsonTypeInfo dictionaryValueInfo = + JsonTypeInfo.ElementTypeInfo // Slower path for non-generic types that implement IDictionary<,>. // It is possible to cache this converter on JsonTypeInfo if we assume the property value // will always be the same type for all instances. - converter = Options.GetConverterInternal(dictionaryValueType); - } + ?? Options.GetTypeInfoInternal(typeof(TValue)); - Debug.Assert(converter != null); - return converter; + Debug.Assert(dictionaryValueInfo is JsonTypeInfo); + return ((JsonTypeInfo)dictionaryValueInfo).EffectiveConverter; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index d7ed57757927b..f75d1da274f30 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -114,7 +114,16 @@ private protected override void SetShouldSerialize(Delegate? predicate) internal override object? DefaultValue => default(T); internal override bool PropertyTypeCanBeNull => default(T) is null; - internal JsonConverter TypedEffectiveConverter { get; private set; } = null!; + internal new JsonConverter EffectiveConverter + { + get + { + Debug.Assert(_typedEffectiveConverter != null); + return _typedEffectiveConverter; + } + } + + private JsonConverter? _typedEffectiveConverter; private protected override void DetermineMemberAccessors(MemberInfo memberInfo) { @@ -203,15 +212,17 @@ internal JsonPropertyInfo(JsonPropertyInfoValues propertyInfo, JsonSerializer private protected override void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo) { - JsonConverter converter = - Options.ExpandConverterFactory(CustomConverter, PropertyType) - ?? jsonTypeInfo.Converter; + Debug.Assert(jsonTypeInfo is JsonTypeInfo); - TypedEffectiveConverter = converter is JsonConverter typedConv ? typedConv : converter.CreateCastingConverter(); - ConverterStrategy = TypedEffectiveConverter.ConverterStrategy; - } + JsonConverter converter = + Options.ExpandConverterFactory(CustomConverter, PropertyType) // Expand any property-level custom converters. + ?.CreateCastingConverter() // Cast to JsonConverter, potentially with wrapping. + ?? ((JsonTypeInfo)jsonTypeInfo).EffectiveConverter; // Fall back to the effective converter for the type. - internal override JsonConverter EffectiveConverter => TypedEffectiveConverter; + _effectiveConverter = converter; + _typedEffectiveConverter = converter; + ConverterStrategy = converter.ConverterStrategy; + } internal override object? GetValueAsObject(object obj) { @@ -232,7 +243,7 @@ internal override bool GetMemberAndWriteJson(object obj, ref WriteStack state, U #if NETCOREAPP !typeof(T).IsValueType && // treated as a constant by recent versions of the JIT. #else - !TypedEffectiveConverter.IsValueType && + !EffectiveConverter.IsValueType && #endif Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.IgnoreCycles && value is not null && @@ -265,7 +276,7 @@ value is not null && { Debug.Assert(PropertyTypeCanBeNull); - if (TypedEffectiveConverter.HandleNullOnWrite) + if (EffectiveConverter.HandleNullOnWrite) { if (state.Current.PropertyState < StackFramePropertyState.Name) { @@ -274,10 +285,10 @@ value is not null && } int originalDepth = writer.CurrentDepth; - TypedEffectiveConverter.Write(writer, value, Options); + EffectiveConverter.Write(writer, value, Options); if (originalDepth != writer.CurrentDepth) { - ThrowHelper.ThrowJsonException_SerializationConverterWrite(TypedEffectiveConverter); + ThrowHelper.ThrowJsonException_SerializationConverterWrite(EffectiveConverter); } } else @@ -295,7 +306,7 @@ value is not null && writer.WritePropertyNameSection(EscapedNameSection); } - return TypedEffectiveConverter.TryWrite(writer, value, Options, ref state); + return EffectiveConverter.TryWrite(writer, value, Options, ref state); } } @@ -317,7 +328,7 @@ internal override bool GetMemberAndWriteJsonExtensionData(object obj, ref WriteS } else { - success = TypedEffectiveConverter.TryWriteDataExtensionProperty(writer, value, Options, ref state); + success = EffectiveConverter.TryWriteDataExtensionProperty(writer, value, Options, ref state); } return success; @@ -328,11 +339,11 @@ internal override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref bool success; bool isNullToken = reader.TokenType == JsonTokenType.Null; - if (isNullToken && !TypedEffectiveConverter.HandleNullOnRead && !state.IsContinuation) + if (isNullToken && !EffectiveConverter.HandleNullOnRead && !state.IsContinuation) { if (default(T) is not null) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypedEffectiveConverter.TypeToConvert); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(EffectiveConverter.TypeToConvert); } if (!IgnoreNullTokensOnRead) @@ -344,7 +355,7 @@ internal override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref success = true; state.Current.MarkRequiredPropertyAsRead(this); } - else if (TypedEffectiveConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null) + else if (EffectiveConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null) { // CanUseDirectReadOrWrite == false when using streams Debug.Assert(!state.IsContinuation); @@ -352,7 +363,7 @@ internal override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref if (!isNullToken || !IgnoreNullTokensOnRead || default(T) is not null) { // Optimize for internal converters by avoiding the extra call to TryRead. - T? fastValue = TypedEffectiveConverter.Read(ref reader, PropertyType, Options); + T? fastValue = EffectiveConverter.Read(ref reader, PropertyType, Options); Set!(obj, fastValue!); } @@ -364,7 +375,7 @@ internal override bool ReadJsonAndSetMember(object obj, ref ReadStack state, ref success = true; if (!isNullToken || !IgnoreNullTokensOnRead || default(T) is not null || state.IsContinuation) { - success = TypedEffectiveConverter.TryRead(ref reader, PropertyType, Options, ref state, out T? value); + success = EffectiveConverter.TryRead(ref reader, PropertyType, Options, ref state, out T? value); if (success) { Set!(obj, value!); @@ -380,11 +391,11 @@ internal override bool ReadJsonAsObject(ref ReadStack state, ref Utf8JsonReader { bool success; bool isNullToken = reader.TokenType == JsonTokenType.Null; - if (isNullToken && !TypedEffectiveConverter.HandleNullOnRead && !state.IsContinuation) + if (isNullToken && !EffectiveConverter.HandleNullOnRead && !state.IsContinuation) { if (default(T) is not null) { - ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypedEffectiveConverter.TypeToConvert); + ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(EffectiveConverter.TypeToConvert); } value = default(T); @@ -393,17 +404,17 @@ internal override bool ReadJsonAsObject(ref ReadStack state, ref Utf8JsonReader else { // Optimize for internal converters by avoiding the extra call to TryRead. - if (TypedEffectiveConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null) + if (EffectiveConverter.CanUseDirectReadOrWrite && state.Current.NumberHandling == null) { // CanUseDirectReadOrWrite == false when using streams Debug.Assert(!state.IsContinuation); - value = TypedEffectiveConverter.Read(ref reader, PropertyType, Options); + value = EffectiveConverter.Read(ref reader, PropertyType, Options); success = true; } else { - success = TypedEffectiveConverter.TryRead(ref reader, PropertyType, Options, ref state, out T? typedValue); + success = EffectiveConverter.TryRead(ref reader, PropertyType, Options, ref state, out T? typedValue); value = typedValue; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index 769ac7add4cd5..de470ca8982ef 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -16,6 +16,11 @@ public abstract class JsonTypeInfo : JsonTypeInfo private Func? _typedCreateObject; + /// + /// A Converter whose declared type always matches that of the current JsonTypeInfo. + /// It might the same instance as JsonTypeInfo.Converter or the same value wrapped + /// in a CastingConverter in cases where a polymorphic converter is being used. + /// internal JsonConverter EffectiveConverter { get; } /// @@ -84,7 +89,7 @@ private protected override void SetCreateObject(Delegate? createObject) internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options) : base(typeof(T), converter, options) { - EffectiveConverter = converter is JsonConverter jsonConverter ? jsonConverter : converter.CreateCastingConverter(); + EffectiveConverter = converter.CreateCastingConverter(); } /// diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs index e62d33b1dd14d..e61319e55caa1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CustomConverterTests/CustomConverterTests.Polymorphic.cs @@ -217,25 +217,30 @@ public static void PersonConverterSerializerPolymorphic() } } - [Fact] - public static void PolymorphicConverter_ShouldWorkInAllContexts() + [Theory] + [MemberData(nameof(PolymorphicConverter_ShouldRoundtripInAllContexts_GetTestData))] + public static void PolymorphicConverter_ShouldRoundtripInAllContexts(T value, string expectedJson) { // Regression test for https://github.com/dotnet/runtime/issues/46522 - - var value = new SampleRepro(); - string expectedJson = "\"string\""; - string json = JsonSerializer.Serialize(value); Assert.Equal(expectedJson, json); - json = JsonSerializer.Serialize(new { Value = value }); - Assert.Equal($@"{{""Value"":{expectedJson}}}", json); + T deserializedValue = JsonSerializer.Deserialize(json); + json = JsonSerializer.Serialize(deserializedValue); + Assert.Equal(expectedJson, json); + } - json = JsonSerializer.Serialize(new[] { value }); - Assert.Equal($"[{expectedJson}]", json); + public static IEnumerable PolymorphicConverter_ShouldRoundtripInAllContexts_GetTestData() + { + var value = new SampleRepro { Value = "string" }; + string expectedJson = "\"string\""; + + yield return WrapArgs(value, expectedJson); + yield return WrapArgs(new { Value = value }, $@"{{""Value"":{expectedJson}}}"); + yield return WrapArgs(new[] { value }, $"[{expectedJson}]"); + yield return WrapArgs(new Dictionary { ["key"] = value }, $@"{{""key"":{expectedJson}}}"); - json = JsonSerializer.Serialize(new Dictionary { ["key"] = value }); - Assert.Equal($@"{{""key"":{expectedJson}}}", json); + static object[] WrapArgs(T value, string expectedJson) => new object[] { value, expectedJson }; } public interface IRepro @@ -246,7 +251,7 @@ public interface IRepro [JsonConverter(typeof(ReproJsonConverter))] public class SampleRepro : IRepro { - public object Value => "string"; + public object Value { get; set; } } public sealed class ReproJsonConverter : JsonConverter> @@ -256,7 +261,10 @@ public override bool CanConvert(Type typeToConvert) return typeof(IRepro).IsAssignableFrom(typeToConvert); } - public override IRepro Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotSupportedException(); + public override IRepro Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new SampleRepro { Value = JsonSerializer.Deserialize(ref reader, options) }; + } public override void Write(Utf8JsonWriter writer, IRepro value, JsonSerializerOptions options) { From 66b37e4903a264bd3d63b39640e09f3c2be07d51 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 3 Aug 2022 21:29:08 +0100 Subject: [PATCH 3/3] Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs --- .../System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs index de470ca8982ef..2530138b839a8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs @@ -18,7 +18,7 @@ public abstract class JsonTypeInfo : JsonTypeInfo /// /// A Converter whose declared type always matches that of the current JsonTypeInfo. - /// It might the same instance as JsonTypeInfo.Converter or the same value wrapped + /// It might be the same instance as JsonTypeInfo.Converter or it could be wrapped /// in a CastingConverter in cases where a polymorphic converter is being used. /// internal JsonConverter EffectiveConverter { get; }