diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index af1136a18e2f2..76086418dd536 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -244,7 +244,7 @@ The requested operation requires an element of type '{0}', but the target element has type '{1}'. - Default TypeInfoResolver and custom TypeInfoResolver after first usage cannot be changed. + Default TypeInfoResolver and custom TypeInfoResolver cannot be changed after first usage. JsonTypeInfo cannot be changed after first usage. @@ -662,4 +662,7 @@ JsonPropertyInfo with name '{0}' for type '{1}' is already bound to different JsonTypeInfo. + + Using JsonTypeInfo for serialization is not possible when TypeInfoResolver has not been set. + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index 63c9afc8207b4..e624b8db4c110 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -655,6 +655,12 @@ internal void InitializeForReflectionSerializer() private JsonTypeInfo? GetTypeInfoInternal(Type type) { IJsonTypeInfoResolver? resolver = _effectiveJsonTypeInfoResolver ?? _typeInfoResolver; + + if (resolver == null) + { + ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoUsedButTypeInfoResolverNotSet(); + } + JsonTypeInfo? info = resolver?.GetTypeInfo(type, this); if (info != null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index c774aaad5bca7..0f08484bdc69a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -116,6 +116,12 @@ public static void ThrowInvalidOperationException_ResolverTypeInfoOptionsNotComp throw new InvalidOperationException(SR.ResolverTypeInfoOptionsNotCompatible); } + [DoesNotReturn] + public static void ThrowInvalidOperationException_JsonTypeInfoUsedButTypeInfoResolverNotSet() + { + throw new InvalidOperationException(SR.JsonTypeInfoUsedButTypeInfoResolverNotSet); + } + [DoesNotReturn] public static void ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(Type classType, MemberInfo? memberInfo) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.cs index a1b1b3d2cc038..97e7f2b6e67cd 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.cs @@ -7,6 +7,7 @@ using System.Text; using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -178,6 +179,79 @@ public static void ModifiersAreCalledAndModifyTypeInfos() Assert.True(secondModifierCalled); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void StaticInitialization_SerializationWithJsonTypeInfoWithoutSettingTypeInfoResolverThrows() + { + RemoteExecutor.Invoke(static () => + { + JsonSerializerOptions o = new(); + DefaultJsonTypeInfoResolver r = new(); + // note: TypeInfoResolver not set + JsonTypeInfo ti = (JsonTypeInfo)r.GetTypeInfo(typeof(SomeClass), o); + SomeClass obj = new() + { + ObjProp = "test", + IntProp = 42, + }; + + // TODO: reasses if this is expected behavior + Assert.Throws(() => JsonSerializer.Serialize(obj, ti)); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void StaticInitialization_DeserializationWithJsonTypeInfoWithoutSettingTypeInfoResolverThrows() + { + RemoteExecutor.Invoke(static () => + { + JsonSerializerOptions o = new(); + DefaultJsonTypeInfoResolver r = new(); + // note: TypeInfoResolver not set + JsonTypeInfo ti = (JsonTypeInfo)r.GetTypeInfo(typeof(SomeClass), o); + + // TODO: reasses if this is expected behavior + string json = """{"ObjProp":"test","IntProp":42}"""; + Assert.Throws(() => JsonSerializer.Deserialize(json, ti)); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void StaticInitialization_SerializationWithJsonTypeInfoWhenTypeInfoResolverSetIsPossible() + { + RemoteExecutor.Invoke(static () => + { + JsonSerializerOptions o = new(); + DefaultJsonTypeInfoResolver r = new(); + o.TypeInfoResolver = r; + JsonTypeInfo ti = (JsonTypeInfo)r.GetTypeInfo(typeof(SomeClass), o); + SomeClass obj = new() + { + ObjProp = "test", + IntProp = 42, + }; + + string json = JsonSerializer.Serialize(obj, ti); + Assert.Equal("""{"ObjProp":"test","IntProp":42}""", json); + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void StaticInitialization_DeserializationWithJsonTypeInfoWhenTypeInfoResolverSetIsPossible() + { + RemoteExecutor.Invoke(static () => + { + JsonSerializerOptions o = new(); + DefaultJsonTypeInfoResolver r = new(); + o.TypeInfoResolver = r; + JsonTypeInfo ti = (JsonTypeInfo)r.GetTypeInfo(typeof(SomeClass), o); + string json = """{"ObjProp":"test","IntProp":42}"""; + SomeClass deserialized = JsonSerializer.Deserialize(json, ti); + Assert.IsType(deserialized.ObjProp); + Assert.Equal("test", ((JsonElement)deserialized.ObjProp).GetString()); + Assert.Equal(42, deserialized.IntProp); + }).Dispose(); + } + private static void InvokeGeneric(Type type, string methodName, params object[] args) { typeof(DefaultJsonTypeInfoResolverTests)