Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -124,6 +128,7 @@ public int KeywordCount
Count(HasDefaultValue);
Count(MinLength != null);
Count(MaxLength != null);
Count(Deprecated != null);

return count;

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -423,6 +424,13 @@ private static JsonSchema MapJsonSchemaCore(

JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema)
{
if (HasObsoleteAttribute(typeInfo.Type) ||
Comment thread
eiriktsarpalis marked this conversation as resolved.
HasObsoleteAttribute(propertyInfo?.AttributeProvider))
{
JsonSchema.EnsureMutable(ref schema);
schema.Deprecated = true;
}

if (schema.Ref is null)
{
if (IsNullableSchema(state.ExporterOptions))
Expand Down Expand Up @@ -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;
}
Comment thread
eiriktsarpalis marked this conversation as resolved.
Comment thread
eiriktsarpalis marked this conversation as resolved.

private static bool IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(JsonTypeInfo typeInfo)
{
Debug.Assert(typeInfo.PolymorphismOptions is not null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MyObsoleteType>(
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<int[]>([1, 2, 3], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"integer"}}""");
yield return new TestData<List<bool>>([false, true, false], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"boolean"}}""");
Expand Down Expand Up @@ -1638,6 +1654,22 @@ public readonly struct StructDictionary<TKey, TValue>(IEnumerable<KeyValuePair<T
IEnumerator IEnumerable.GetEnumerator() => ((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>(
T? Value,
string ExpectedJsonSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>))]
Expand Down
Loading