From 24b07eb9e4bc9581717bcb2f7f95c63a482bc533 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 6 Aug 2025 15:01:22 -0700 Subject: [PATCH 1/8] Add extension method for JsonSerializerOptions.GetTypeInfo --- .../System.Text.Json/ref/System.Text.Json.cs | 4 ++++ .../src/System.Text.Json.csproj | 1 + .../JsonSerializerOptionsExtensions.cs | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index cea226f6fadf40..709c61ec36f6e1 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -463,6 +463,10 @@ public void MakeReadOnly() { } public void MakeReadOnly(bool populateMissingResolver) { } public bool TryGetTypeInfo(System.Type type, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Text.Json.Serialization.Metadata.JsonTypeInfo? typeInfo) { throw null; } } + public static class JsonSerializerOptionsExtensions + { + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) { throw null; } + } public enum JsonTokenType : byte { None = (byte)0, diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index e8106e1f215008..f8eeafddaf9d96 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -269,6 +269,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs new file mode 100644 index 00000000000000..5df5a5108fc011 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Metadata; + +namespace System.Text.Json; + +/// +/// Extension methods for . +/// +public static class JsonSerializerOptionsExtensions +{ + /// + /// Gets the contract metadata resolved by the current instance. + /// + /// The contract metadata resolved for . + /// + /// If the instance is locked for modification, the method will return a cached instance for the metadata. + /// + public static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) + => (JsonTypeInfo)options.GetTypeInfo(typeof(T)); +} From 6be4c2aed4a67889654ef43faa0bef4fca785822 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 12 Aug 2025 12:20:51 -0700 Subject: [PATCH 2/8] Respond to API review comments --- .../System.Text.Json/ref/System.Text.Json.cs | 6 ++--- .../src/System.Text.Json.csproj | 1 - .../JsonSerializerOptions.Caching.cs | 24 +++++++++++++++++++ .../JsonSerializerOptionsExtensions.cs | 22 ----------------- 4 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 709c61ec36f6e1..fcf7c3cb807857 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -457,15 +457,13 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Getting a converter for a type may require reflection which depends on unreferenced code.")] public System.Text.Json.Serialization.JsonConverter GetConverter(System.Type typeToConvert) { throw null; } public System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(System.Type type) { throw null; } + public System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo() { throw null; } public void MakeReadOnly() { } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Populating unconfigured TypeInfoResolver properties with the reflection resolver requires runtime code generation.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Populating unconfigured TypeInfoResolver properties with the reflection resolver requires unreferenced code.")] public void MakeReadOnly(bool populateMissingResolver) { } public bool TryGetTypeInfo(System.Type type, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Text.Json.Serialization.Metadata.JsonTypeInfo? typeInfo) { throw null; } - } - public static class JsonSerializerOptionsExtensions - { - public static System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) { throw null; } + public bool TryGetTypeInfo([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Text.Json.Serialization.Metadata.JsonTypeInfo? typeInfo) { throw null; } } public enum JsonTokenType : byte { diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index f8eeafddaf9d96..e8106e1f215008 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -269,7 +269,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET - diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index 8fda42bee9345f..839d2ebde92cb1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -64,6 +64,15 @@ public JsonTypeInfo GetTypeInfo(Type type) return GetTypeInfoInternal(type, resolveIfMutable: true); } + /// + /// Gets the contract metadata resolved by the current instance. + /// + /// The contract metadata resolved for . + /// + /// If the instance is locked for modification, the method will return a cached instance for the metadata. + /// + public JsonTypeInfo GetTypeInfo() => (JsonTypeInfo)GetTypeInfo(typeof(T)); + /// /// Tries to get the contract metadata resolved by the current instance. /// @@ -90,6 +99,21 @@ public bool TryGetTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? type return typeInfo is not null; } + /// + /// Tries to get the contract metadata resolved by the current instance. + /// + /// The resolved contract metadata, or if not contract could be resolved. + /// if a contract for was found, or otherwise. + /// is not valid for serialization. + /// + /// If the instance is locked for modification, the method will return a cached instance for the metadata. + /// + public bool TryGetTypeInfo([NotNullWhen(true)] out JsonTypeInfo? typeInfo) + { + typeInfo = (JsonTypeInfo?)GetTypeInfo(typeof(T)); + return typeInfo is not null; + } + /// /// Same as GetTypeInfo but without validation and additional knobs. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs deleted file mode 100644 index 5df5a5108fc011..00000000000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptionsExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text.Json.Serialization.Metadata; - -namespace System.Text.Json; - -/// -/// Extension methods for . -/// -public static class JsonSerializerOptionsExtensions -{ - /// - /// Gets the contract metadata resolved by the current instance. - /// - /// The contract metadata resolved for . - /// - /// If the instance is locked for modification, the method will return a cached instance for the metadata. - /// - public static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options) - => (JsonTypeInfo)options.GetTypeInfo(typeof(T)); -} From 9de066b2098e03e9283fe4c22da02f12167ea4cc Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 12 Aug 2025 14:00:00 -0700 Subject: [PATCH 3/8] Adjust implementation --- .../Serialization/JsonSerializerOptions.Caching.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index 839d2ebde92cb1..6bb3192b4afca7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -102,7 +102,7 @@ public bool TryGetTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? type /// /// Tries to get the contract metadata resolved by the current instance. /// - /// The resolved contract metadata, or if not contract could be resolved. + /// The resolved contract metadata, or if no contract could be resolved. /// if a contract for was found, or otherwise. /// is not valid for serialization. /// @@ -110,8 +110,13 @@ public bool TryGetTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? type /// public bool TryGetTypeInfo([NotNullWhen(true)] out JsonTypeInfo? typeInfo) { - typeInfo = (JsonTypeInfo?)GetTypeInfo(typeof(T)); - return typeInfo is not null; + if (TryGetTypeInfo(typeof(T), out var typeInfoOpt)) + { + typeInfo = (JsonTypeInfo)typeInfoOpt; + return true; + } + typeInfo = null; + return false; } /// From 9689fd559ce97fe681f451ccd042ff201bad9c01 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 15 Aug 2025 11:56:01 -0700 Subject: [PATCH 4/8] Apply suggestion from @PranavSenthilnathan Co-authored-by: Pranav Senthilnathan --- .../Text/Json/Serialization/JsonSerializerOptions.Caching.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index 6bb3192b4afca7..e0f03fd09e6b1f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -110,7 +110,7 @@ public bool TryGetTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? type /// public bool TryGetTypeInfo([NotNullWhen(true)] out JsonTypeInfo? typeInfo) { - if (TryGetTypeInfo(typeof(T), out var typeInfoOpt)) + if (TryGetTypeInfo(typeof(T), out JsonTypeInfo? typeInfoOpt)) { typeInfo = (JsonTypeInfo)typeInfoOpt; return true; From c4b8fa97879a38ec8a0a7380e661f602946a1c1a Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 15 Aug 2025 11:56:09 -0700 Subject: [PATCH 5/8] Apply suggestion from @PranavSenthilnathan Co-authored-by: Pranav Senthilnathan --- .../Text/Json/Serialization/JsonSerializerOptions.Caching.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index e0f03fd09e6b1f..2e5228cf0fbd56 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -65,7 +65,7 @@ public JsonTypeInfo GetTypeInfo(Type type) } /// - /// Gets the contract metadata resolved by the current instance. + /// Gets the contract metadata resolved by the current instance. /// /// The contract metadata resolved for . /// From 0e442bf7569b9bfc5ea3e959397df4025b03f9bf Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 15 Aug 2025 11:57:00 -0700 Subject: [PATCH 6/8] Update JsonSerializerOptions.Caching.cs --- .../Text/Json/Serialization/JsonSerializerOptions.Caching.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index 2e5228cf0fbd56..65a4f74b0cfe56 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -100,7 +100,7 @@ public bool TryGetTypeInfo(Type type, [NotNullWhen(true)] out JsonTypeInfo? type } /// - /// Tries to get the contract metadata resolved by the current instance. + /// Tries to get the contract metadata resolved by the current instance. /// /// The resolved contract metadata, or if no contract could be resolved. /// if a contract for was found, or otherwise. From 1a5c2e46b3d167c4b19b61db09beba693b7b29e1 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 15 Aug 2025 11:57:22 -0700 Subject: [PATCH 7/8] Apply suggestion from @PranavSenthilnathan Co-authored-by: Pranav Senthilnathan --- .../Text/Json/Serialization/JsonSerializerOptions.Caching.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs index 65a4f74b0cfe56..6555a255f4b87b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs @@ -115,6 +115,7 @@ public bool TryGetTypeInfo([NotNullWhen(true)] out JsonTypeInfo? typeInfo) typeInfo = (JsonTypeInfo)typeInfoOpt; return true; } + typeInfo = null; return false; } From 8dadcae6635fb9fda74566d5e071502ebb8cdd10 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Thu, 28 Aug 2025 16:41:32 -0700 Subject: [PATCH 8/8] Add tests and add serialization context overloads --- .../System.Text.Json/ref/System.Text.Json.cs | 4 ++++ .../Json/Serialization/JsonSerializerContext.cs | 8 ++++++++ .../Metadata/IJsonTypeInfoResolver.cs | 13 +++++++++++++ .../JsonSerializerContextTests.cs | 12 ++++++++++-- .../Serialization/OptionsTests.cs | 14 ++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index fcf7c3cb807857..506abd2f3abd92 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -1154,6 +1154,7 @@ protected JsonSerializerContext(System.Text.Json.JsonSerializerOptions? options) protected abstract System.Text.Json.JsonSerializerOptions? GeneratedSerializerOptions { get; } public System.Text.Json.JsonSerializerOptions Options { get { throw null; } } public abstract System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type); + public System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo() { throw null; } System.Text.Json.Serialization.Metadata.JsonTypeInfo System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver.GetTypeInfo(System.Type type, System.Text.Json.JsonSerializerOptions options) { throw null; } } [System.FlagsAttribute] @@ -1272,6 +1273,9 @@ public DefaultJsonTypeInfoResolver() { } public partial interface IJsonTypeInfoResolver { System.Text.Json.Serialization.Metadata.JsonTypeInfo? GetTypeInfo(System.Type type, System.Text.Json.JsonSerializerOptions options); +#if NET + System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo() { throw null; } +#endif } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public sealed partial class JsonCollectionInfoValues diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs index b13763ae624c18..a5ba60e9a65469 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerContext.cs @@ -107,6 +107,14 @@ protected JsonSerializerContext(JsonSerializerOptions? options) /// The metadata for the specified type, or if the context has no metadata for the type. public abstract JsonTypeInfo? GetTypeInfo(Type type); + + /// + /// Returns a instance representing the given type. + /// + /// The type to fetch metadata about. + /// The metadata for the specified type, or if the context has no metadata for the type. + public JsonTypeInfo? GetTypeInfo() => (JsonTypeInfo?)GetTypeInfo(typeof(T)); + JsonTypeInfo? IJsonTypeInfoResolver.GetTypeInfo(Type type, JsonSerializerOptions options) { if (options != null && options != _options) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs index 4acabe53d731fa..bc397b5dfec35d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/IJsonTypeInfoResolver.cs @@ -18,5 +18,18 @@ public interface IJsonTypeInfoResolver /// or if no contract could be resolved. /// JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options); + +#if NET + /// + /// Resolves a contract for the requested type and options. + /// + /// Configuration used when resolving the metadata. + /// + /// A instance matching the requested type, + /// or if no contract could be resolved. + /// + JsonTypeInfo? GetTypeInfo(JsonSerializerOptions options) + => (JsonTypeInfo?)GetTypeInfo(typeof(T), options); +#endif } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs index df27fc881b9229..6c4fdf394fd3f5 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSerializerContextTests.cs @@ -88,6 +88,14 @@ public static void IJsonTypeInfoResolver_GetTypeInfo_MetadataIsMutable() Assert.Equal(2, PersonJsonContext.Default.Person.Properties.Count); } + [Fact] + public static void TypeInfoIsSameAsGeneric() + { + var t1Info = (JsonTypeInfo)PersonJsonContext.Default.GetTypeInfo(typeof(Person)); + var t2Info = PersonJsonContext.Default.GetTypeInfo(); + Assert.Same(t1Info, t2Info); + } + [Fact] public static void VariousGenericsAreSupported() { @@ -215,9 +223,9 @@ public static void SupportsReservedLanguageKeywordsAsFields() var options = new JsonSerializerOptions { IncludeFields = true }; GreetingCardWithFields card = new() {@event = "Birthday", message = @"Happy Birthday!"}; - + byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(card, GreetingCardWithFieldsJsonContext.Default.GreetingCardWithFields); - + card = JsonSerializer.Deserialize(utf8Json, GreetingCardWithFieldsJsonContext.Default.GreetingCardWithFields); Assert.Equal("Happy Birthday!", card.message); Assert.Equal("Birthday", card.@event); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs index 402ae6c8117d96..cc27a4c6750753 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs @@ -674,6 +674,20 @@ public static void Options_JsonSerializerContext_DoesNotFallbackToReflection() Assert.Throws(() => JsonSerializer.Serialize(unsupportedValue, options)); } + [Fact] + public static void Options_GetTypeInfoGeneric() + { + var options = JsonContext.Default.Options; + var t1Info = (JsonTypeInfo)options.GetTypeInfo(typeof(WeatherForecastWithPOCOs)); + var t2Info = options.GetTypeInfo(); + Assert.Same(t1Info, t2Info); + + Assert.True(options.TryGetTypeInfo(typeof(WeatherForecastWithPOCOs), out JsonTypeInfo? t3Info)); + Assert.True(options.TryGetTypeInfo(out var t4Info)); + Assert.Same(t1Info, t3Info); + Assert.Same(t3Info, t4Info); + } + [Fact] public static void JsonSerializer_IsReflectionEnabledByDefault_DefaultsToTrue() {