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 0948acc19bb922..8ffc12bd077926 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 @@ -26,8 +26,8 @@ internal sealed class JsonSchema internal const string MinLengthPropertyName = "minLength"; internal const string MaxLengthPropertyName = "maxLength"; - public static JsonSchema False { get; } = new(false); - public static JsonSchema True { get; } = new(true); + public static JsonSchema CreateFalseSchema() => new(false); + public static JsonSchema CreateTrueSchema() => new(true); public JsonSchema() { } private JsonSchema(bool trueOrFalse) { _trueOrFalse = trueOrFalse; } @@ -279,7 +279,7 @@ public static void EnsureMutable(ref JsonSchema schema) switch (schema._trueOrFalse) { case false: - schema = new JsonSchema { Not = True }; + schema = new JsonSchema { Not = CreateTrueSchema() }; break; case true: schema = new JsonSchema(); 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 0e8fa55dc64939..0551255273b833 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 @@ -211,7 +211,7 @@ private static JsonSchema MapJsonSchemaCore( JsonUnmappedMemberHandling effectiveUnmappedMemberHandling = typeInfo.UnmappedMemberHandling ?? typeInfo.Options.UnmappedMemberHandling; if (effectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow) { - additionalProperties = JsonSchema.False; + additionalProperties = JsonSchema.CreateFalseSchema(); } if (typeDiscriminator is { } typeDiscriminatorPair) @@ -350,7 +350,7 @@ private static JsonSchema MapJsonSchemaCore( default: Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.None); // Return a `true` schema for types with user-defined converters. - return CompleteSchema(ref state, JsonSchema.True); + return CompleteSchema(ref state, JsonSchema.CreateTrueSchema()); } JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs index 4f9e1a6e39b6fc..a88039e2a42117 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs @@ -80,6 +80,6 @@ public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerialize return node; } - internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs index 97dbea8bbf7a9e..b912ed898b42bd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs @@ -31,6 +31,6 @@ public override void Write(Utf8JsonWriter writer, JsonValue? value, JsonSerializ return JsonValue.CreateFromElement(ref element, options.GetNodeOptions()); } - internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs index bda21c258fbe0e..27839a36f88f7f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs @@ -147,6 +147,6 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, return true; } - internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs index fd964f09800f9e..9fbb293639152c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs @@ -26,6 +26,6 @@ public override void Write(Utf8JsonWriter writer, JsonDocument? value, JsonSeria value.WriteTo(writer); } - internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs index 79e8ef4bf280d7..718d9fa8024630 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs @@ -18,6 +18,6 @@ public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSeriali value.WriteTo(writer); } - internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema(); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs index f80af7c92b6c74..37281cd9557ff1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs @@ -21,6 +21,6 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions throw new NotSupportedException(ErrorMessage); internal override JsonSchema? GetSchema(JsonNumberHandling _) => - new JsonSchema { Comment = "Unsupported .NET type", Not = JsonSchema.True }; + new JsonSchema { Comment = "Unsupported .NET type", Not = JsonSchema.CreateTrueSchema() }; } } 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 e128d6e6e474c5..f89625be6da824 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs @@ -1102,6 +1102,18 @@ of the type which points to the first occurrence. */ } """); + yield return new TestData( + Value: new() { Prop1 = new() , Prop2 = new() }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Prop1": true, + "Prop2": true, + } + } + """); + // 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"}}"""); @@ -1586,6 +1598,29 @@ public interface ITestData IEnumerable GetTestDataForAllValues(); } + public class ClassWithPropertiesUsingCustomConverters + { + [JsonPropertyOrder(0)] + public ClassWithCustomConverter1 Prop1 { get; set; } + [JsonPropertyOrder(1)] + public ClassWithCustomConverter2 Prop2 { get; set; } + + [JsonConverter(typeof(CustomConverter))] + public class ClassWithCustomConverter1; + + [JsonConverter(typeof(CustomConverter))] + public class ClassWithCustomConverter2; + + public sealed class CustomConverter : JsonConverter + { + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => default; + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + => writer.WriteNullValue(); + } + } + private static TAttribute? GetCustomAttribute(ICustomAttributeProvider? provider, bool inherit = false) where TAttribute : Attribute => provider?.GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute; } diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs index 5bc3c542246662..8fdaf7bf2fc01f 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs @@ -98,6 +98,32 @@ public void CanGenerateXElementSchema() Assert.True(schema.ToJsonString().Length < 100_000); } + [Fact] + public void TransformSchemaNode_PropertiesWithCustomConverters() + { + // Regression test for https://github.com/dotnet/runtime/issues/109868 + List<(Type? ParentType, string? PropertyName, Type type)> visitedNodes = new(); + JsonSchemaExporterOptions exporterOptions = new() + { + TransformSchemaNode = (ctx, schema) => + { + visitedNodes.Add((ctx.PropertyInfo?.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type)); + return schema; + } + }; + + List<(Type? ParentType, string? PropertyName, Type type)> expectedNodes = + [ + (typeof(ClassWithPropertiesUsingCustomConverters), "Prop1", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter1)), + (typeof(ClassWithPropertiesUsingCustomConverters), "Prop2", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter2)), + (null, null, typeof(ClassWithPropertiesUsingCustomConverters)), + ]; + + Serializer.DefaultOptions.GetJsonSchemaAsNode(typeof(ClassWithPropertiesUsingCustomConverters), exporterOptions); + + Assert.Equal(expectedNodes, visitedNodes); + } + [Fact] public void TypeWithDisallowUnmappedMembers_AdditionalPropertiesFailValidation() { 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 01f3b7747fedf2..c091d09b56a009 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 @@ -110,6 +110,7 @@ public sealed partial class JsonSchemaExporterTests_SourceGen() [JsonSerializable(typeof(ClassWithComponentModelAttributes))] [JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))] [JsonSerializable(typeof(ClassWithOptionalObjectParameter))] + [JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))] // Collection types [JsonSerializable(typeof(int[]))] [JsonSerializable(typeof(List))]