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..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,23 @@ 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) + { + 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))]