From 5f259690fdb577cb40644a4e6602dbbfeac7d147 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 25 Sep 2024 18:27:46 +0100 Subject: [PATCH 1/2] Fix handling of appending keywords to boolean schemas. --- .../src/System/Text/Json/Schema/JsonSchema.cs | 13 +++++++++++++ .../Text/Json/Schema/JsonSchemaExporter.cs | 1 + .../Common/JsonSchemaExporterTests.TestTypes.cs | 17 +++++++++++++++++ .../Serialization/JsonSchemaExporterTests.cs | 1 + 4 files changed, 32 insertions(+) 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 d261b374b390a..ed58285262159 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 @@ -260,6 +260,19 @@ JsonNode CompleteSchema(JsonNode schema) } } + public static void EnsureMutable(ref JsonSchema schema) + { + switch (schema._trueOrFalse) + { + case false: + schema = new JsonSchema { Not = True }; + break; + case true: + schema = new JsonSchema(); + break; + } + } + private static ReadOnlySpan s_schemaValues => [ // NB the order of these values influences order of types in the rendered 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 063bd95147673..9690b2b308def 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 @@ -240,6 +240,7 @@ private static JsonSchema MapJsonSchemaCore( if (property.AssociatedParameter is { HasDefaultValue: true } parameterInfo) { + JsonSchema.EnsureMutable(ref propertySchema); propertySchema.DefaultValue = JsonSerializer.SerializeToNode(parameterInfo.DefaultValue, property.JsonTypeInfo); propertySchema.HasDefaultValue = true; } 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 12ee630b378fb..0f247cab034bc 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs @@ -1034,6 +1034,18 @@ public static IEnumerable GetTestDataCore() } """); + yield return new TestData( + Value: new(value: null), + AdditionalValues: [new(true), new(42), new(""), new(new object()), new(Array.Empty())], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Value": { "default": null } + } + } + """); + // 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"}}"""); @@ -1441,6 +1453,11 @@ public class ClassWithJsonPointerEscapablePropertyNames public PocoWithRecursiveMembers Value { get; set; } } + public class ClassWithOptionalObjectParameter(object? value = null) + { + public object? Value { get; } = value; + } + public readonly struct StructDictionary(IEnumerable> values) : IReadOnlyDictionary where TKey : notnull 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 a7b0775361de9..6946ea661b561 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 @@ -107,6 +107,7 @@ public sealed partial class JsonSchemaExporterTests_SourceGen() [JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))] [JsonSerializable(typeof(ClassWithComponentModelAttributes))] [JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))] + [JsonSerializable(typeof(ClassWithOptionalObjectParameter))] // Collection types [JsonSerializable(typeof(int[]))] [JsonSerializable(typeof(List))] From e973d371791ec3ab9cdc15d4b038942ce02e4bcb Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 26 Sep 2024 14:25:36 +0100 Subject: [PATCH 2/2] Add a few comments documenting boolean schemas. --- .../src/System/Text/Json/Schema/JsonSchema.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 ed58285262159..0948acc19bb92 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 @@ -34,6 +34,14 @@ public JsonSchema() { } public bool IsTrue => _trueOrFalse is true; public bool IsFalse => _trueOrFalse is false; + + /// + /// Per the JSON schema core specification section 4.3 + /// (https://json-schema.org/draft/2020-12/json-schema-core#name-json-schema-documents) + /// A JSON schema must either be an object or a boolean. + /// We represent false and true schemas using this flag. + /// It is not possible to specify keywords in boolean schemas. + /// private readonly bool? _trueOrFalse; public string? Ref { get => _ref; set { VerifyMutable(); _ref = value; } } @@ -95,6 +103,7 @@ public int KeywordCount { if (_trueOrFalse != null) { + // Boolean schemas admit no keywords return 0; } @@ -129,6 +138,7 @@ public void MakeNullable() { if (_trueOrFalse != null) { + // boolean schemas do not admit type keywords. return; } @@ -260,6 +270,10 @@ JsonNode CompleteSchema(JsonNode schema) } } + /// + /// If the schema is boolean, replaces it with a semantically + /// equivalent object schema that allows appending keywords. + /// public static void EnsureMutable(ref JsonSchema schema) { switch (schema._trueOrFalse)