diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs index 8ffc12bd077926..45ee76419a480d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs @@ -25,6 +25,7 @@ internal sealed class JsonSchema internal const string DefaultPropertyName = "default"; internal const string MinLengthPropertyName = "minLength"; internal const string MaxLengthPropertyName = "maxLength"; + internal const string DeprecatedPropertyName = "deprecated"; public static JsonSchema CreateFalseSchema() => new(false); public static JsonSchema CreateTrueSchema() => new(true); @@ -95,6 +96,9 @@ public JsonSchema() { } public int? MaxLength { get => _maxLength; set { VerifyMutable(); _maxLength = value; } } private int? _maxLength; + public bool? Deprecated { get => _deprecated; set { VerifyMutable(); _deprecated = value; } } + private bool? _deprecated; + public JsonSchemaExporterContext? ExporterContext { get; set; } public int KeywordCount @@ -124,6 +128,7 @@ public int KeywordCount Count(HasDefaultValue); Count(MinLength != null); Count(MaxLength != null); + Count(Deprecated != null); return count; @@ -255,6 +260,11 @@ public JsonNode ToJsonNode(JsonSchemaExporterOptions options) objSchema.Add(MaxLengthPropertyName, (JsonNode)maxLength); } + if (Deprecated is { } deprecated) + { + objSchema.Add(DeprecatedPropertyName, (JsonNode)deprecated); + } + return CompleteSchema(objSchema); JsonNode CompleteSchema(JsonNode schema) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs index 1ac5814543c963..04c01ffda9c50e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Reflection; using System.Runtime.InteropServices; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -423,6 +424,13 @@ private static JsonSchema MapJsonSchemaCore( JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema) { + if (HasObsoleteAttribute(typeInfo.Type) || + HasObsoleteAttribute(propertyInfo?.AttributeProvider)) + { + JsonSchema.EnsureMutable(ref schema); + schema.Deprecated = true; + } + if (schema.Ref is null) { if (IsNullableSchema(state.ExporterOptions)) @@ -471,6 +479,28 @@ private static void ValidateOptions(JsonSerializerOptions options) options.MakeReadOnly(); } + private static bool HasObsoleteAttribute(ICustomAttributeProvider? attributeProvider) + { + if (attributeProvider is null) + { + return false; + } + + // Identify ObsoleteAttribute using its full type name rather than typeof(ObsoleteAttribute). + // On downlevel targets System.Text.Json compiles in an internal ObsoleteAttribute polyfill + // that would otherwise shadow the framework type, causing the typeof comparison to never match + // the ObsoleteAttribute applied by user code. + foreach (object attribute in attributeProvider.GetCustomAttributes(inherit: true)) + { + if (attribute.GetType().FullName == "System.ObsoleteAttribute") + { + return true; + } + } + + return false; + } + private static bool IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(JsonTypeInfo typeInfo) { Debug.Assert(typeInfo.PolymorphismOptions is not null); diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs index 065f18e501d5ec..6bb37ddbb827fa 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs @@ -1193,6 +1193,22 @@ of the type which points to the first occurrence. */ } """); +#pragma warning disable CS0612 // Type or member is obsolete + yield return new TestData( + Value: new() { MyString = "str", MyObsoleteString = "str", MyObsoleteInnerType = new() }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "MyString": { "type": ["string","null"] }, + "MyObsoleteString": { "type": ["string","null"], "deprecated": true }, + "MyObsoleteInnerType": { "type": ["object","null"], "deprecated": true } + }, + "deprecated": true + } + """); +#pragma warning restore CS0612 // Type or member is obsolete + // Collection types yield return new TestData([1, 2, 3], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"integer"}}"""); yield return new TestData>([false, true, false], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"boolean"}}"""); @@ -1638,6 +1654,22 @@ public readonly struct StructDictionary(IEnumerable ((IEnumerable)_dictionary).GetEnumerator(); } + [Obsolete] + public sealed class MyObsoleteType + { + public string? MyString { get; set; } + + [Obsolete] + public string? MyObsoleteString { get; set; } + + public MyInnerObsoleteType? MyObsoleteInnerType { get; set; } + + [Obsolete] + public sealed class MyInnerObsoleteType + { + } + } + public record TestData( T? Value, string ExpectedJsonSchema, diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs index f4cf65cb00a8b9..67eea71653bd49 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs @@ -117,6 +117,9 @@ public sealed partial class JsonSchemaExporterTests_SourceGen() [JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))] [JsonSerializable(typeof(ClassWithOptionalObjectParameter))] [JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))] +#pragma warning disable CS0612 // Type or member is obsolete + [JsonSerializable(typeof(MyObsoleteType))] +#pragma warning restore CS0612 // Type or member is obsolete // Collection types [JsonSerializable(typeof(int[]))] [JsonSerializable(typeof(List))]