File: JsonSchemaExporter\TestTypes.cs
Web Access
Project: src\test\Shared\Shared.Tests.csproj (Shared.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Schema;
using System.Text.Json.Serialization;
using System.Xml.Linq;
 
#pragma warning disable SA1118 // Parameter should not span multiple lines
#pragma warning disable JSON001 // Comments not allowed
#pragma warning disable S2344 // Enumeration type names should not have "Flags" or "Enum" suffixes
#pragma warning disable SA1502 // Element should not be on a single line
#pragma warning disable SA1136 // Enum values should be on separate lines
#pragma warning disable SA1133 // Do not combine attributes
#pragma warning disable S3604 // Member initializer values should not be redundant
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable
#pragma warning disable S1121 // Assignments should not be made from within sub-expressions
#pragma warning disable IDE0073 // The file header is missing or not located at the top of the file
 
namespace Microsoft.Extensions.AI.JsonSchemaExporter;
 
public static partial class TestTypes
{
    public static IEnumerable<object[]> GetTestData() => GetTestDataCore().Select(t => new object[] { t });
 
    public static IEnumerable<object[]> GetTestDataUsingAllValues() =>
        GetTestDataCore()
        .SelectMany(t => t.GetTestDataForAllValues())
        .Select(t => new object[] { t });
 
    public static IEnumerable<ITestData> GetTestDataCore()
    {
        // Primitives and built-in types
        yield return new TestData<object>(
            Value: new(),
            AdditionalValues: [42, false, 3.14, 3.14M, new int[] { 1, 2, 3 }, new SimpleRecord(1, "str", false, 3.14)],
            ExpectedJsonSchema: "true"true");
 
        yield return new TestData<bool>(true, """{"type":"boolean"}"""{"type":"boolean"}""");
        yield return new TestData<byte>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<ushort>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<uint>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<ulong>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<sbyte>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<short>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<int>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<long>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<float>(1.2f, """{"type":"number"}"""{"type":"number"}""");
        yield return new TestData<double>(3.14159d, """{"type":"number"}"""{"type":"number"}""");
        yield return new TestData<decimal>(3.14159M, """{"type":"number"}"""{"type":"number"}""");
#if NET7_0_OR_GREATER
        yield return new TestData<UInt128>(42, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<Int128>(42, """{"type":"integer"}"""{"type":"integer"}""");
#endif
#if NET6_0_OR_GREATER
        yield return new TestData<Half>((Half)3.141, """{"type":"number"}"""{"type":"number"}""");
#endif
        yield return new TestData<string>("I am a string", """{"type":["string","null"]}"""{"type":["string","null"]}""");
        yield return new TestData<char>('c', """{"type":"string","minLength":1,"maxLength":1}"""{"type":"string","minLength":1,"maxLength":1}""");
        yield return new TestData<byte[]>(
            Value: [1, 2, 3],
            AdditionalValues: [[]],
            ExpectedJsonSchema: """{"type":["string","null"]}"""{"type":["string","null"]}""");
 
        yield return new TestData<Memory<byte>>(new byte[] { 1, 2, 3 }, """{"type":"string"}"""{"type":"string"}""");
        yield return new TestData<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }, """{"type":"string"}"""{"type":"string"}""");
        yield return new TestData<DateTime>(
            Value: new(2021, 1, 1),
            AdditionalValues: [DateTime.MinValue, DateTime.MaxValue],
            ExpectedJsonSchema: """{"type":"string","format": "date-time"}"""{"type":"string","format": "date-time"}""");
 
        yield return new TestData<DateTimeOffset>(
            Value: new(new DateTime(2021, 1, 1), TimeSpan.Zero),
            AdditionalValues: [DateTimeOffset.MinValue, DateTimeOffset.MaxValue],
            ExpectedJsonSchema: """{"type":"string","format": "date-time"}"""{"type":"string","format": "date-time"}""");
 
        yield return new TestData<TimeSpan>(
            Value: new(hours: 5, minutes: 13, seconds: 3),
            AdditionalValues: [TimeSpan.MinValue, TimeSpan.MaxValue],
            ExpectedJsonSchema: """{"$comment": "Represents a System.TimeSpan value.", "type":"string", "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$"}"""{"$comment": "Represents a System.TimeSpan value.", "type":"string", "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$"}""");
 
#if NET6_0_OR_GREATER
        yield return new TestData<DateOnly>(new(2021, 1, 1), """{"type":"string","format": "date"}"""{"type":"string","format": "date"}""");
        yield return new TestData<TimeOnly>(new(hour: 22, minute: 30, second: 33, millisecond: 100), """{"type":"string","format": "time"}"""{"type":"string","format": "time"}""");
#endif
        yield return new TestData<Guid>(Guid.Empty, """{"type":"string","format":"uuid"}"""{"type":"string","format":"uuid"}""");
        yield return new TestData<Uri>(new("http://example.com"), """{"type":["string","null"], "format":"uri"}"""{"type":["string","null"], "format":"uri"}""");
        yield return new TestData<Version>(new(1, 2, 3, 4), """{"$comment":"Represents a version string.", "type":["string","null"],"pattern":"^\\d+(\\.\\d+){1,3}$"}"""{"$comment":"Represents a version string.", "type":["string","null"],"pattern":"^\\d+(\\.\\d+){1,3}$"}""");
        yield return new TestData<JsonDocument>(JsonDocument.Parse("""[{ "x" : 42 }]"""[{ "x" : 42 }]"""), "true"true");
        yield return new TestData<JsonElement>(JsonDocument.Parse("""[{ "x" : 42 }]"""[{ "x" : 42 }]""").RootElement, "true"true");
        yield return new TestData<JsonNode>(JsonNode.Parse("""[{ "x" : 42 }]"""[{ "x" : 42 }]"""), "true"true");
        yield return new TestData<JsonValue>((JsonValue)42, "true"true");
        yield return new TestData<JsonObject>(new() { ["x"] = 42 }, """{"type":["object","null"]}"""{"type":["object","null"]}""");
        yield return new TestData<JsonArray>([1, 2, 3], """{"type":["array","null"]}"""{"type":["array","null"]}""");
 
        // Enum types
        yield return new TestData<IntEnum>(IntEnum.A, """{"type":"integer"}"""{"type":"integer"}""");
        yield return new TestData<StringEnum>(StringEnum.A, """{"enum": ["A","B","C"]}"""{"enum": ["A","B","C"]}""");
        yield return new TestData<FlagsStringEnum>(FlagsStringEnum.A, """{"type":"string"}"""{"type":"string"}""");
 
        // Nullable<T> types
        yield return new TestData<bool?>(true, """{"type":["boolean","null"]}"""{"type":["boolean","null"]}""");
        yield return new TestData<int?>(42, """{"type":["integer","null"]}"""{"type":["integer","null"]}""");
        yield return new TestData<double?>(3.14, """{"type":["number","null"]}"""{"type":["number","null"]}""");
        yield return new TestData<Guid?>(Guid.Empty, """{"type":["string","null"],"format":"uuid"}"""{"type":["string","null"],"format":"uuid"}""");
        yield return new TestData<JsonElement?>(JsonDocument.Parse("{}"{}").RootElement, "true"true");
        yield return new TestData<IntEnum?>(IntEnum.A, """{"type":["integer","null"]}"""{"type":["integer","null"]}""");
        yield return new TestData<StringEnum?>(StringEnum.A, """{"enum":["A","B","C",null]}"""{"enum":["A","B","C",null]}""");
        yield return new TestData<SimpleRecordStruct?>(
            new(1, "two", true, 3.14),
            ExpectedJsonSchema: """
            {
                "type":["object","null"],
                "properties": {
                    "X": {"type":"integer"},
                    "Y": {"type":"string"},
                    "Z": {"type":"boolean"},
                    "W": {"type":"number"}
                }
            }
            """{
                "type":["object","null"],
                "properties": {
                    "X": {"type":"integer"},
                    "Y": {"type":"string"},
                    "Z": {"type":"boolean"},
                    "W": {"type":"number"}
                }
            }
            """);
 
        // User-defined POCOs
        yield return new TestData<SimplePoco>(
            Value: new() { String = "string", StringNullable = "string", Int = 42, Double = 3.14, Boolean = true },
            AdditionalValues: [new() { String = "str", StringNullable = null }],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "String": { "type": "string" },
                    "StringNullable": { "type": ["string", "null"] },
                    "Int": { "type": "integer" },
                    "Double": { "type": "number" },
                    "Boolean": { "type": "boolean" }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "String": { "type": "string" },
                    "StringNullable": { "type": ["string", "null"] },
                    "Int": { "type": "integer" },
                    "Double": { "type": "number" },
                    "Boolean": { "type": "boolean" }
                }
            }
            """);
 
        // Same as above but with nullable types set to non-nullable
        yield return new TestData<SimplePoco>(
            Value: new() { String = "string", StringNullable = "string", Int = 42, Double = 3.14, Boolean = true },
            AdditionalValues: [new() { String = "str", StringNullable = null }],
            ExpectedJsonSchema: """
            {
                "type": "object",
                "properties": {
                    "String": { "type": "string" },
                    "StringNullable": { "type": ["string", "null"] },
                    "Int": { "type": "integer" },
                    "Double": { "type": "number" },
                    "Boolean": { "type": "boolean" }
                }
            }
            """{
                "type": "object",
                "properties": {
                    "String": { "type": "string" },
                    "StringNullable": { "type": ["string", "null"] },
                    "Int": { "type": "integer" },
                    "Double": { "type": "number" },
                    "Boolean": { "type": "boolean" }
                }
            }
            """,
            ExporterOptions: new() { TreatNullObliviousAsNonNullable = true });
 
        yield return new TestData<SimpleRecord>(
            Value: new(1, "two", true, 3.14),
            ExpectedJsonSchema: """
            {
              "type": ["object","null"],
              "properties": {
                "X": { "type": "integer" },
                "Y": { "type": "string" },
                "Z": { "type": "boolean" },
                "W": { "type": "number" }
              },
              "required": ["X","Y","Z","W"]
            }
            """{
              "type": ["object","null"],
              "properties": {
                "X": { "type": "integer" },
                "Y": { "type": "string" },
                "Z": { "type": "boolean" },
                "W": { "type": "number" }
              },
              "required": ["X","Y","Z","W"]
            }
            """);
 
        yield return new TestData<SimpleRecordStruct>(
            Value: new(1, "two", true, 3.14),
            ExpectedJsonSchema: """
            {
              "type": "object",
              "properties": {
                "X": { "type": "integer" },
                "Y": { "type": "string" },
                "Z": { "type": "boolean" },
                "W": { "type": "number" }
              }
            }
            """{
              "type": "object",
              "properties": {
                "X": { "type": "integer" },
                "Y": { "type": "string" },
                "Z": { "type": "boolean" },
                "W": { "type": "number" }
              }
            }
            """);
 
        yield return new TestData<RecordWithOptionalParameters>(
            Value: new(1, "two", true, 3.14, StringEnum.A),
            ExpectedJsonSchema: """
            {
              "type": ["object","null"],
              "properties": {
                "X1": { "type": "integer" },
                "X2": { "type": "string" },
                "X3": { "type": "boolean" },
                "X4": { "type": "number" },
                "X5": { "enum": ["A", "B", "C"] },
                "Y1": { "type": "integer", "default": 42 },
                "Y2": { "type": "string", "default": "str" },
                "Y3": { "type": "boolean", "default": true },
                "Y4": { "type": "number", "default": 0 },
                "Y5": { "enum": ["A", "B", "C"], "default": "A" }
              },
              "required": ["X1", "X2", "X3", "X4", "X5"]
            }
            """{
              "type": ["object","null"],
              "properties": {
                "X1": { "type": "integer" },
                "X2": { "type": "string" },
                "X3": { "type": "boolean" },
                "X4": { "type": "number" },
                "X5": { "enum": ["A", "B", "C"] },
                "Y1": { "type": "integer", "default": 42 },
                "Y2": { "type": "string", "default": "str" },
                "Y3": { "type": "boolean", "default": true },
                "Y4": { "type": "number", "default": 0 },
                "Y5": { "enum": ["A", "B", "C"], "default": "A" }
              },
              "required": ["X1", "X2", "X3", "X4", "X5"]
            }
            """);
 
        yield return new TestData<PocoWithRequiredMembers>(
            new() { X = "str1", Y = "str2" },
            ExpectedJsonSchema: """
            {
              "type": ["object","null"],
              "properties": {
                "Y": { "type": "string" },
                "Z": { "type": "integer" },
                "X": { "type": "string" }
              },
              "required": [ "Y", "Z", "X" ]
            }
            """{
              "type": ["object","null"],
              "properties": {
                "Y": { "type": "string" },
                "Z": { "type": "integer" },
                "X": { "type": "string" }
              },
              "required": [ "Y", "Z", "X" ]
            }
            """);
 
        yield return new TestData<PocoWithIgnoredMembers>(
            new() { X = 1, Y = 2 },
            ExpectedJsonSchema: """
            {
              "type": [ "object", "null" ],
              "properties": {
                "X": { "type": "integer" }
              }
            }
            """{
              "type": [ "object", "null" ],
              "properties": {
                "X": { "type": "integer" }
              }
            }
            """);
        yield return new TestData<PocoWithCustomNaming>(
            Value: new() { IntegerProperty = 1, StringProperty = "str" },
            ExpectedJsonSchema: """
            {
              "type": ["object","null"],
              "properties": {
                "int": { "type": "integer" },
                "str": { "type": [ "string", "null"] }
              }
            }
            """{
              "type": ["object","null"],
              "properties": {
                "int": { "type": "integer" },
                "str": { "type": [ "string", "null"] }
              }
            }
            """);
 
        yield return new TestData<PocoWithCustomNumberHandling>(
            Value: new() { X = 1 },
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": { "X": { "type": ["string","integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" } }
            }
            """{
                "type": ["object","null"],
                "properties": { "X": { "type": ["string","integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" } }
            }
            """);
 
        yield return new TestData<PocoWithCustomNumberHandlingOnProperties>(
            Value: new() { X = 1, Y = 2, Z = 3 },
            AdditionalValues: [
                new() { X = 1, Y = double.NaN, Z = 3 },
                new() { X = 1, Y = double.PositiveInfinity, Z = 3 },
                new() { X = 1, Y = double.NegativeInfinity, Z = 3 },
            ],
            WritesNumbersAsStrings: true,
            ExpectedJsonSchema: """
            {
              "type": ["object","null"],
              "properties": {
                "X": { "type": ["string", "integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" },
                "Y": {
                  "anyOf": [
                    { "type": "number" },
                    { "enum": ["NaN", "Infinity", "-Infinity"]}
                  ]
                },
                "Z": { "type": ["string", "integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" },
                "W" : { "type": "number" }
              }
            }
            """{
              "type": ["object","null"],
              "properties": {
                "X": { "type": ["string", "integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" },
                "Y": {
                  "anyOf": [
                    { "type": "number" },
                    { "enum": ["NaN", "Infinity", "-Infinity"]}
                  ]
                },
                "Z": { "type": ["string", "integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" },
                "W" : { "type": "number" }
              }
            }
            """);
 
        yield return new TestData<PocoWithRecursiveMembers>(
            Value: new() { Value = 1, Next = new() { Value = 2, Next = new() { Value = 3 } } },
            AdditionalValues: [new() { Value = 1, Next = null }],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "Value": { "type": "integer" },
                    "Next": {
                        "type": ["object","null"],
                        "properties": {
                            "Value": { "type": "integer" },
                            "Next": { "$ref": "#/properties/Next" }
                        }
                    }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "Value": { "type": "integer" },
                    "Next": {
                        "type": ["object","null"],
                        "properties": {
                            "Value": { "type": "integer" },
                            "Next": { "$ref": "#/properties/Next" }
                        }
                    }
                }
            }
            """);
 
        // Same as above but with non-nullable reference types by default.
        yield return new TestData<PocoWithRecursiveMembers>(
            Value: new() { Value = 1, Next = new() { Value = 2, Next = new() { Value = 3 } } },
            AdditionalValues: [new() { Value = 1, Next = null }],
            ExpectedJsonSchema: """
            {
                "type": "object",
                "properties": {
                    "Value": { "type": "integer" },
                    "Next": {
                        "type": ["object","null"],
                        "properties": {
                            "Value": { "type": "integer" },
                            "Next": { "$ref": "#/properties/Next" }
                        }
                    }
                }
            }
            """{
                "type": "object",
                "properties": {
                    "Value": { "type": "integer" },
                    "Next": {
                        "type": ["object","null"],
                        "properties": {
                            "Value": { "type": "integer" },
                            "Next": { "$ref": "#/properties/Next" }
                        }
                    }
                }
            }
            """,
            ExporterOptions: new() { TreatNullObliviousAsNonNullable = true });
 
#if !NET9_0 // Disable until https://github.com/dotnet/runtime/pull/108764 gets backported
        SimpleRecord recordValue = new(42, "str", true, 3.14);
        yield return new TestData<PocoWithNonRecursiveDuplicateOccurrences>(
            Value: new() { Value1 = recordValue, Value2 = recordValue, ArrayValue = [recordValue], ListValue = [recordValue] },
            ExpectedJsonSchema: """
                {
                  "type": ["object","null"],
                  "properties": {
                    "Value1": {
                      "type": ["object","null"],
                      "properties": {
                        "X": { "type": "integer" },
                        "Y": { "type": "string" },
                        "Z": { "type": "boolean" },
                        "W": { "type": "number" }
                      },
                      "required": ["X", "Y", "Z", "W"]
                    },
                    /* The same type on a different property is repeated to
                       account for potential metadata resolved from attributes. */
                    "Value2": {
                      "type": ["object","null"],
                      "properties": {
                        "X": { "type": "integer" },
                        "Y": { "type": "string" },
                        "Z": { "type": "boolean" },
                        "W": { "type": "number" }
                      },
                      "required": ["X", "Y", "Z", "W"]
                    },
                    /* This collection element is the first occurrence
                       of the type without contextual metadata. */
                    "ListValue": {
                      "type": ["array","null"],
                      "items": {
                        "type": ["object","null"],
                        "properties": {
                          "X": { "type": "integer" },
                          "Y": { "type": "string" },
                          "Z": { "type": "boolean" },
                          "W": { "type": "number" }
                        },
                        "required": ["X", "Y", "Z", "W"]
                      }
                    },
                    /* This collection element is the second occurrence
                       of the type which points to the first occurrence. */
                    "ArrayValue": {
                      "type": ["array","null"],
                      "items": {
                        "$ref": "#/properties/ListValue/items"
                      }
                    }
                  }
                }
                """{
                  "type": ["object","null"],
                  "properties": {
                    "Value1": {
                      "type": ["object","null"],
                      "properties": {
                        "X": { "type": "integer" },
                        "Y": { "type": "string" },
                        "Z": { "type": "boolean" },
                        "W": { "type": "number" }
                      },
                      "required": ["X", "Y", "Z", "W"]
                    },
                    /* The same type on a different property is repeated to
                       account for potential metadata resolved from attributes. */
                    "Value2": {
                      "type": ["object","null"],
                      "properties": {
                        "X": { "type": "integer" },
                        "Y": { "type": "string" },
                        "Z": { "type": "boolean" },
                        "W": { "type": "number" }
                      },
                      "required": ["X", "Y", "Z", "W"]
                    },
                    /* This collection element is the first occurrence
                       of the type without contextual metadata. */
                    "ListValue": {
                      "type": ["array","null"],
                      "items": {
                        "type": ["object","null"],
                        "properties": {
                          "X": { "type": "integer" },
                          "Y": { "type": "string" },
                          "Z": { "type": "boolean" },
                          "W": { "type": "number" }
                        },
                        "required": ["X", "Y", "Z", "W"]
                      }
                    },
                    /* This collection element is the second occurrence
                       of the type which points to the first occurrence. */
                    "ArrayValue": {
                      "type": ["array","null"],
                      "items": {
                        "$ref": "#/properties/ListValue/items"
                      }
                    }
                  }
                }
                """);
#endif
 
        yield return new TestData<PocoWithDescription>(
            Value: new() { X = 42 },
            ExpectedJsonSchema: """
            {
              "type": ["object","null"],
              "properties": {
                "X": {
                  "type": "integer"
                }
              }
            }
            """{
              "type": ["object","null"],
              "properties": {
                "X": {
                  "type": "integer"
                }
              }
            }
            """);
 
        yield return new TestData<PocoWithCustomConverter>(new() { Value = 42 }, "true"true");
        yield return new TestData<PocoWithCustomPropertyConverter>(new() { Value = 42 }, """{"type":["object","null"],"properties":{"Value":true}}"""{"type":["object","null"],"properties":{"Value":true}}""");
        yield return new TestData<PocoWithEnums>(
            Value: new()
            {
                IntEnum = IntEnum.A,
                StringEnum = StringEnum.B,
                IntEnumUsingStringConverter = IntEnum.A,
                NullableIntEnumUsingStringConverter = IntEnum.B,
                StringEnumUsingIntConverter = StringEnum.A,
                NullableStringEnumUsingIntConverter = StringEnum.B
            },
            AdditionalValues: [
                new()
                {
                    IntEnum = (IntEnum)int.MaxValue,
                    StringEnum = StringEnum.A,
                    IntEnumUsingStringConverter = IntEnum.A,
                    NullableIntEnumUsingStringConverter = null,
                    StringEnumUsingIntConverter = (StringEnum)int.MaxValue,
                    NullableStringEnumUsingIntConverter = null
                },
            ],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "IntEnum": { "type": "integer" },
                    "StringEnum": { "enum": [ "A", "B", "C" ] },
                    "IntEnumUsingStringConverter": { "enum": [ "A", "B", "C" ] },
                    "NullableIntEnumUsingStringConverter": { "enum": [ "A", "B", "C", null ] },
                    "StringEnumUsingIntConverter": { "type": "integer" },
                    "NullableStringEnumUsingIntConverter": { "type": [ "integer", "null" ] }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "IntEnum": { "type": "integer" },
                    "StringEnum": { "enum": [ "A", "B", "C" ] },
                    "IntEnumUsingStringConverter": { "enum": [ "A", "B", "C" ] },
                    "NullableIntEnumUsingStringConverter": { "enum": [ "A", "B", "C", null ] },
                    "StringEnumUsingIntConverter": { "type": "integer" },
                    "NullableStringEnumUsingIntConverter": { "type": [ "integer", "null" ] }
                }
            }
            """);
 
        var recordStruct = new SimpleRecordStruct(42, "str", true, 3.14);
        yield return new TestData<PocoWithStructFollowedByNullableStruct>(
            Value: new() { Struct = recordStruct, NullableStruct = null },
            AdditionalValues: [new() { Struct = recordStruct, NullableStruct = recordStruct }],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "Struct": {
                        "type": "object",
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    },
                    "NullableStruct": {
                        "type": ["object","null"],
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "Struct": {
                        "type": "object",
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    },
                    "NullableStruct": {
                        "type": ["object","null"],
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    }
                }
            }
            """);
 
        yield return new TestData<PocoWithNullableStructFollowedByStruct>(
            Value: new() { NullableStruct = null, Struct = recordStruct },
            AdditionalValues: [new() { NullableStruct = recordStruct, Struct = recordStruct }],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "NullableStruct": {
                        "type": ["object","null"],
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    },
                    "Struct": {
                        "type": "object",
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "NullableStruct": {
                        "type": ["object","null"],
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    },
                    "Struct": {
                        "type": "object",
                        "properties": {
                            "X": {"type":"integer"},
                            "Y": {"type":"string"},
                            "Z": {"type":"boolean"},
                            "W": {"type":"number"}
                        }
                    }
                }
            }
            """);
 
        yield return new TestData<PocoWithExtensionDataProperty>(
            Value: new() { Name = "name", ExtensionData = new() { ["x"] = 42 } },
            """{"type":["object","null"],"properties":{"Name":{"type":["string","null"]}}}"""{"type":["object","null"],"properties":{"Name":{"type":["string","null"]}}}""");
 
        yield return new TestData<PocoDisallowingUnmappedMembers>(
            Value: new() { Name = "name", Age = 42 },
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "Name": {"type":["string","null"]},
                    "Age": {"type":"integer"}
                },
                "additionalProperties": false
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "Name": {"type":["string","null"]},
                    "Age": {"type":"integer"}
                },
                "additionalProperties": false
            }
            """);
 
        // Global JsonUnmappedMemberHandling.Disallow setting
        yield return new TestData<SimplePoco>(
            Value: new() { String = "string", StringNullable = "string", Int = 42, Double = 3.14, Boolean = true },
            AdditionalValues: [new() { String = "str", StringNullable = null }],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "String": { "type": "string" },
                    "StringNullable": { "type": ["string", "null"] },
                    "Int": { "type": "integer" },
                    "Double": { "type": "number" },
                    "Boolean": { "type": "boolean" }
                },
                "additionalProperties": false
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "String": { "type": "string" },
                    "StringNullable": { "type": ["string", "null"] },
                    "Int": { "type": "integer" },
                    "Double": { "type": "number" },
                    "Boolean": { "type": "boolean" }
                },
                "additionalProperties": false
            }
            """,
            Options: new() { UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow });
 
        yield return new TestData<PocoWithNullableAnnotationAttributes>(
            Value: new() { MaybeNull = null!, AllowNull = null, NotNull = null, DisallowNull = null!, NotNullDisallowNull = "str" },
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "MaybeNull": {"type":["string","null"]},
                    "AllowNull": {"type":["string","null"]},
                    "NotNull": {"type":["string","null"]},
                    "DisallowNull": {"type":["string","null"]},
                    "NotNullDisallowNull": {"type":"string"}
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "MaybeNull": {"type":["string","null"]},
                    "AllowNull": {"type":["string","null"]},
                    "NotNull": {"type":["string","null"]},
                    "DisallowNull": {"type":["string","null"]},
                    "NotNullDisallowNull": {"type":"string"}
                }
            }
            """);
 
        yield return new TestData<PocoWithNullableAnnotationAttributesOnConstructorParams>(
            Value: new(allowNull: null, disallowNull: "str"),
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "AllowNull": {"type":["string","null"]},
                    "DisallowNull": {"type":"string"}
                },
                "required": ["AllowNull", "DisallowNull"]
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "AllowNull": {"type":["string","null"]},
                    "DisallowNull": {"type":"string"}
                },
                "required": ["AllowNull", "DisallowNull"]
            }
            """);
 
        yield return new TestData<PocoWithNullableConstructorParameter>(
            Value: new(null),
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "Value": {"type":["string","null"]}
                },
                "required": ["Value"]
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "Value": {"type":["string","null"]}
                },
                "required": ["Value"]
            }
            """);
 
        yield return new TestData<PocoWithOptionalConstructorParams>(
            Value: new(),
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "X1": {"type":"string", "default": "str" },
                    "X2": {"type":"integer", "default": 42 },
                    "X3": {"type":"boolean", "default": true },
                    "X4": {"type":"number", "default": 0 },
                    "X5": {"enum":["A","B","C"], "default": "A" },
                    "X6": {"type":["string","null"], "default": "str" },
                    "X7": {"type":["integer","null"], "default": 42 },
                    "X8": {"type":["boolean","null"], "default": true },
                    "X9": {"type":["number","null"], "default": 0 },
                    "X10": {"enum":["A","B","C", null], "default": "A" }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "X1": {"type":"string", "default": "str" },
                    "X2": {"type":"integer", "default": 42 },
                    "X3": {"type":"boolean", "default": true },
                    "X4": {"type":"number", "default": 0 },
                    "X5": {"enum":["A","B","C"], "default": "A" },
                    "X6": {"type":["string","null"], "default": "str" },
                    "X7": {"type":["integer","null"], "default": 42 },
                    "X8": {"type":["boolean","null"], "default": true },
                    "X9": {"type":["number","null"], "default": 0 },
                    "X10": {"enum":["A","B","C", null], "default": "A" }
                }
            }
            """);
 
        yield return new TestData<GenericPocoWithNullableConstructorParameter<string>>(
            Value: new(null!),
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "Value": {"type":["string","null"]}
                },
                "required": ["Value"]
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "Value": {"type":["string","null"]}
                },
                "required": ["Value"]
            }
            """);
 
        yield return new TestData<PocoWithPolymorphism>(
            Value: new PocoWithPolymorphism.DerivedPocoStringDiscriminator { BaseValue = 42, DerivedValue = "derived" },
            AdditionalValues: [
                new PocoWithPolymorphism.DerivedPocoNoDiscriminator { BaseValue = 42, DerivedValue = "derived" },
                new PocoWithPolymorphism.DerivedPocoIntDiscriminator { BaseValue = 42, DerivedValue = "derived" },
                new PocoWithPolymorphism.DerivedCollection { BaseValue = 42 },
                new PocoWithPolymorphism.DerivedDictionary { BaseValue = 42 },
            ],
 
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "anyOf": [
                    {
                        "properties": {
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        }
                    },
                    {
                        "properties": {
                            "$type": {"const":"derivedPoco"},
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        },
                        "required": ["$type"]
                    },
                    {
                        "properties": {
                            "$type": {"const":42},
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        },
                        "required": ["$type"]
                    },
                    {
                        "properties": {
                            "$type": {"const":"derivedCollection"},
                            "$values": {
                                "type": "array",
                                "items": {"type":"integer"}
                            }
                        },
                        "required": ["$type"]
                    },
                    {
                        "properties": {
                            "$type": {"const":"derivedDictionary"}
                        },
                        "additionalProperties":{"type": "integer"},
                        "required": ["$type"]
                    }
                ]
            }
            """{
                "type": ["object","null"],
                "anyOf": [
                    {
                        "properties": {
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        }
                    },
                    {
                        "properties": {
                            "$type": {"const":"derivedPoco"},
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        },
                        "required": ["$type"]
                    },
                    {
                        "properties": {
                            "$type": {"const":42},
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        },
                        "required": ["$type"]
                    },
                    {
                        "properties": {
                            "$type": {"const":"derivedCollection"},
                            "$values": {
                                "type": "array",
                                "items": {"type":"integer"}
                            }
                        },
                        "required": ["$type"]
                    },
                    {
                        "properties": {
                            "$type": {"const":"derivedDictionary"}
                        },
                        "additionalProperties":{"type": "integer"},
                        "required": ["$type"]
                    }
                ]
            }
            """);
 
        yield return new TestData<NonAbstractClassWithSingleDerivedType>(
            Value: new NonAbstractClassWithSingleDerivedType(),
            AdditionalValues: [new NonAbstractClassWithSingleDerivedType.Derived()],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"]
            }
            """{
                "type": ["object","null"]
            }
            """);
 
#if !NET9_0 // Disable until https://github.com/microsoft/semantic-kernel/issues/8983 gets backported to .NET 9
        yield return new TestData<ClassWithOptionalObjectParameter>(
            Value: new(value: null),
            AdditionalValues: [new(true), new(42), new(""), new(new object()), new(Array.Empty<int>())],
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                  "Value": { "default": null }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                  "Value": { "default": null }
                }
            }
            """);
#endif
 
        yield return new TestData<PocoCombiningPolymorphicTypeAndDerivedTypes>(
            Value: new(),
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "PolymorphicValue": {
                        "type": "object",
                        "anyOf": [
                            {
                                "properties": {
                                    "BaseValue": {"type":"integer"},
                                    "DerivedValue": {"type":["string","null"]}
                                }
                            },
                            {
                                "properties": {
                                    "$type": {"const":"derivedPoco"},
                                    "BaseValue": {"type":"integer"},
                                    "DerivedValue": {"type":["string","null"]}
                                },
                                "required": ["$type"]
                            },
                            {
                                "properties": {
                                    "$type": {"const":42},
                                    "BaseValue": {"type":"integer"},
                                    "DerivedValue": {"type":["string","null"]}
                                },
                                "required": ["$type"]
                            },
                            {
                                "properties": {
                                    "$type": {"const":"derivedCollection"},
                                    "$values": {
                                        "type": "array",
                                        "items": {"type":"integer"}
                                    }
                                },
                                "required": ["$type"]
                            },
                            {
                                "properties": {
                                    "$type": {"const":"derivedDictionary"}
                                },
                                "additionalProperties":{"type": "integer"},
                                "required": ["$type"]
                            }
                        ]
                    },
                    "DerivedValue1": { 
                        "type": "object",
                        "properties": {
                            "BaseValue": {
                                "type": "integer"
                            },
                            "DerivedValue": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        }
                    },
                    "DerivedValue2": {
                        "type": "object",
                        "properties": {
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        }
                    }
                }
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "PolymorphicValue": {
                        "type": "object",
                        "anyOf": [
                            {
                                "properties": {
                                    "BaseValue": {"type":"integer"},
                                    "DerivedValue": {"type":["string","null"]}
                                }
                            },
                            {
                                "properties": {
                                    "$type": {"const":"derivedPoco"},
                                    "BaseValue": {"type":"integer"},
                                    "DerivedValue": {"type":["string","null"]}
                                },
                                "required": ["$type"]
                            },
                            {
                                "properties": {
                                    "$type": {"const":42},
                                    "BaseValue": {"type":"integer"},
                                    "DerivedValue": {"type":["string","null"]}
                                },
                                "required": ["$type"]
                            },
                            {
                                "properties": {
                                    "$type": {"const":"derivedCollection"},
                                    "$values": {
                                        "type": "array",
                                        "items": {"type":"integer"}
                                    }
                                },
                                "required": ["$type"]
                            },
                            {
                                "properties": {
                                    "$type": {"const":"derivedDictionary"}
                                },
                                "additionalProperties":{"type": "integer"},
                                "required": ["$type"]
                            }
                        ]
                    },
                    "DerivedValue1": { 
                        "type": "object",
                        "properties": {
                            "BaseValue": {
                                "type": "integer"
                            },
                            "DerivedValue": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        }
                    },
                    "DerivedValue2": {
                        "type": "object",
                        "properties": {
                            "BaseValue": {"type":"integer"},
                            "DerivedValue": {"type":["string","null"]}
                        }
                    }
                }
            }
            """);
 
        yield return new TestData<ClassWithComponentModelAttributes>(
            Value: new("string", -1),
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "properties": {
                    "StringValue": {"type":"string","pattern":"\\w+"},
                    "IntValue": {"type":"integer","default":42}
                },
                "required": ["StringValue","IntValue"]
            }
            """{
                "type": ["object","null"],
                "properties": {
                    "StringValue": {"type":"string","pattern":"\\w+"},
                    "IntValue": {"type":"integer","default":42}
                },
                "required": ["StringValue","IntValue"]
            }
            """,
            ExporterOptions: new()
            {
                TransformSchemaNode = static (ctx, schema) =>
                {
                    if (ctx.PropertyInfo is null || schema is not JsonObject jObj)
                    {
                        return schema;
                    }
 
                    if (ctx.ResolveAttribute<DefaultValueAttribute>() is { } attr)
                    {
                        jObj["default"] = JsonSerializer.SerializeToNode(attr.Value);
                    }
 
                    if (ctx.ResolveAttribute<RegularExpressionAttribute>() is { } regexAttr)
                    {
                        jObj["pattern"] = regexAttr.Pattern;
                    }
 
                    return jObj;
                }
            });
 
        // Collection types
        yield return new TestData<int[]>([1, 2, 3], """{"type":["array","null"],"items":{"type":"integer"}}"""{"type":["array","null"],"items":{"type":"integer"}}""");
        yield return new TestData<List<bool>>([false, true, false], """{"type":["array","null"],"items":{"type":"boolean"}}"""{"type":["array","null"],"items":{"type":"boolean"}}""");
        yield return new TestData<HashSet<string>>(["one", "two", "three"], """{"type":["array","null"],"items":{"type":["string","null"]}}"""{"type":["array","null"],"items":{"type":["string","null"]}}""");
        yield return new TestData<Queue<double>>(new([1.1, 2.2, 3.3]), """{"type":["array","null"],"items":{"type":"number"}}"""{"type":["array","null"],"items":{"type":"number"}}""");
        yield return new TestData<Stack<char>>(new(['x', '2', '+']), """{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":1}}"""{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":1}}""");
        yield return new TestData<ImmutableArray<int>>(ImmutableArray.Create(1, 2, 3), """{"type":"array","items":{"type":"integer"}}"""{"type":"array","items":{"type":"integer"}}""");
        yield return new TestData<ImmutableList<string>>(ImmutableList.Create("one", "two", "three"), """{"type":["array","null"],"items":{"type":["string","null"]}}"""{"type":["array","null"],"items":{"type":["string","null"]}}""");
        yield return new TestData<ImmutableQueue<bool>>(ImmutableQueue.Create(false, false, true), """{"type":["array","null"],"items":{"type":"boolean"}}"""{"type":["array","null"],"items":{"type":"boolean"}}""");
        yield return new TestData<object[]>([1, "two", 3.14], """{"type":["array","null"]}"""{"type":["array","null"]}""");
        yield return new TestData<System.Collections.ArrayList>([1, "two", 3.14], """{"type":["array","null"]}"""{"type":["array","null"]}""");
 
        // Dictionary types
        yield return new TestData<Dictionary<string, int>>(
            Value: new() { ["one"] = 1, ["two"] = 2, ["three"] = 3 },
            ExpectedJsonSchema: """{"type":["object","null"],"additionalProperties":{"type": "integer"}}"""{"type":["object","null"],"additionalProperties":{"type": "integer"}}""");
 
        yield return new TestData<StructDictionary<string, int>>(
            Value: new([new("one", 1), new("two", 2), new("three", 3)]),
            ExpectedJsonSchema: """{"type":"object","additionalProperties":{"type": "integer"}}"""{"type":"object","additionalProperties":{"type": "integer"}}""");
 
        yield return new TestData<SortedDictionary<int, string>>(
            Value: new() { [1] = "one", [2] = "two", [3] = "three" },
            ExpectedJsonSchema: """{"type":["object","null"],"additionalProperties":{"type": ["string","null"]}}"""{"type":["object","null"],"additionalProperties":{"type": ["string","null"]}}""");
 
        yield return new TestData<Dictionary<string, SimplePoco>>(
            Value: new()
            {
                ["one"] = new() { String = "string", StringNullable = "string", Int = 42, Double = 3.14, Boolean = true },
                ["two"] = new() { String = "string", StringNullable = null, Int = 42, Double = 3.14, Boolean = true },
                ["three"] = new() { String = "string", StringNullable = null, Int = 42, Double = 3.14, Boolean = true },
            },
            ExpectedJsonSchema: """
            {
                "type": ["object","null"],
                "additionalProperties": {
                    "properties": {
                        "String": { "type": "string" },
                        "StringNullable": { "type": ["string", "null"] },
                        "Int": { "type": "integer" },
                        "Double": { "type": "number" },
                        "Boolean": { "type": "boolean" }
                    },
                    "type": ["object","null"]
                }
            }
            """{
                "type": ["object","null"],
                "additionalProperties": {
                    "properties": {
                        "String": { "type": "string" },
                        "StringNullable": { "type": ["string", "null"] },
                        "Int": { "type": "integer" },
                        "Double": { "type": "number" },
                        "Boolean": { "type": "boolean" }
                    },
                    "type": ["object","null"]
                }
            }
            """);
 
        yield return new TestData<Dictionary<string, object>>(
            Value: new() { ["one"] = 1, ["two"] = "two", ["three"] = 3.14 },
            ExpectedJsonSchema: """{"type":["object","null"]}"""{"type":["object","null"]}""");
 
        yield return new TestData<Hashtable>(
            Value: new() { ["one"] = 1, ["two"] = "two", ["three"] = 3.14 },
            ExpectedJsonSchema: """{"type":["object","null"]}"""{"type":["object","null"]}""");
    }
 
    public enum IntEnum { A, B, C }
 
    [JsonConverter(typeof(JsonStringEnumConverter<StringEnum>))]
    public enum StringEnum { A, B, C }
 
    [Flags, JsonConverter(typeof(JsonStringEnumConverter<FlagsStringEnum>))]
    public enum FlagsStringEnum { A = 1, B = 2, C = 4 }
 
    public class SimplePoco
    {
        public string String { get; set; } = "default";
        public string? StringNullable { get; set; }
 
        public int Int { get; set; }
        public double Double { get; set; }
        public bool Boolean { get; set; }
    }
 
    public record SimpleRecord(int X, string Y, bool Z, double W);
    public record struct SimpleRecordStruct(int X, string Y, bool Z, double W);
 
    public record RecordWithOptionalParameters(
        [property: Description("required integer")] int X1, string X2, bool X3, double X4, [Description("required string enum")] StringEnum X5,
        [property: Description("optional integer")] int Y1 = 42, string Y2 = "str", bool Y3 = true, double Y4 = 0, [Description("optional string enum")] StringEnum Y5 = StringEnum.A);
 
    public class PocoWithRequiredMembers
    {
        [JsonInclude]
        public required string X;
 
        public required string Y { get; set; }
 
        [JsonRequired]
        public int Z { get; set; }
    }
 
    public class PocoWithIgnoredMembers
    {
        public int X { get; set; }
 
        [JsonIgnore]
        public int Y { get; set; }
    }
 
    public class PocoWithCustomNaming
    {
        [JsonPropertyName("int")]
        public int IntegerProperty { get; set; }
 
        [JsonPropertyName("str")]
        public string? StringProperty { get; set; }
    }
 
    [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
    public class PocoWithCustomNumberHandling
    {
        public int X { get; set; }
    }
 
    public class PocoWithCustomNumberHandlingOnProperties
    {
        [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
        public int X { get; set; }
 
        [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)]
        public double Y { get; set; }
 
        [JsonNumberHandling(JsonNumberHandling.WriteAsString)]
        public int Z { get; set; }
 
        [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)]
        public decimal W { get; set; }
    }
 
    public class PocoWithRecursiveMembers
    {
        public int Value { get; init; }
        public PocoWithRecursiveMembers? Next { get; init; }
    }
 
    public class PocoWithNonRecursiveDuplicateOccurrences
    {
        public SimpleRecord? Value1 { get; set; }
        public SimpleRecord? Value2 { get; set; }
        public List<SimpleRecord>? ListValue { get; set; }
        public SimpleRecord[]? ArrayValue { get; set; }
    }
 
    [Description("The type description")]
    public class PocoWithDescription
    {
        [Description("The property description")]
        public int X { get; set; }
    }
 
    [JsonConverter(typeof(CustomConverter))]
    public class PocoWithCustomConverter
    {
        public int Value { get; set; }
 
        public class CustomConverter : JsonConverter<PocoWithCustomConverter>
        {
            public override PocoWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
                new PocoWithCustomConverter { Value = reader.GetInt32() };
 
            public override void Write(Utf8JsonWriter writer, PocoWithCustomConverter value, JsonSerializerOptions options) =>
                writer.WriteNumberValue(value.Value);
        }
    }
 
    public class PocoWithCustomPropertyConverter
    {
        [JsonConverter(typeof(CustomConverter))]
        public int Value { get; set; }
 
        public class CustomConverter : JsonConverter<int>
        {
            public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
                => int.Parse(reader.GetString()!);
 
            public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
                => writer.WriteStringValue(value.ToString());
        }
    }
 
    public class PocoWithEnums
    {
        public IntEnum IntEnum { get; init; }
        public StringEnum StringEnum { get; init; }
 
        [JsonConverter(typeof(JsonStringEnumConverter<IntEnum>))]
        public IntEnum IntEnumUsingStringConverter { get; set; }
 
        [JsonConverter(typeof(JsonStringEnumConverter<IntEnum>))]
        public IntEnum? NullableIntEnumUsingStringConverter { get; set; }
 
        [JsonConverter(typeof(JsonNumberEnumConverter<StringEnum>))]
        public StringEnum StringEnumUsingIntConverter { get; set; }
 
        [JsonConverter(typeof(JsonNumberEnumConverter<StringEnum>))]
        public StringEnum? NullableStringEnumUsingIntConverter { get; set; }
    }
 
    public class PocoWithStructFollowedByNullableStruct
    {
        public SimpleRecordStruct? NullableStruct { get; set; }
        public SimpleRecordStruct Struct { get; set; }
    }
 
    public class PocoWithNullableStructFollowedByStruct
    {
        public SimpleRecordStruct? NullableStruct { get; set; }
        public SimpleRecordStruct Struct { get; set; }
    }
 
    public class PocoWithExtensionDataProperty
    {
        public string? Name { get; set; }
 
        [JsonExtensionData]
        public Dictionary<string, object>? ExtensionData { get; set; }
    }
 
    [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
    public class PocoDisallowingUnmappedMembers
    {
        public string? Name { get; set; }
        public int Age { get; set; }
    }
 
    public class PocoWithNullableAnnotationAttributes
    {
        [MaybeNull]
        public string MaybeNull { get; set; }
 
        [AllowNull]
        public string AllowNull { get; set; }
 
        [NotNull]
        public string? NotNull { get; set; }
 
        [DisallowNull]
        public string? DisallowNull { get; set; }
 
        [NotNull, DisallowNull]
        public string? NotNullDisallowNull { get; set; } = "";
    }
 
    public class PocoWithNullableAnnotationAttributesOnConstructorParams([AllowNull] string allowNull, [DisallowNull] string? disallowNull)
    {
        public string AllowNull { get; } = allowNull!;
        public string DisallowNull { get; } = disallowNull;
    }
 
    public class PocoWithNullableConstructorParameter(string? value)
    {
        public string Value { get; } = value!;
    }
 
    public class PocoWithOptionalConstructorParams(
        string x1 = "str", int x2 = 42, bool x3 = true, double x4 = 0, StringEnum x5 = StringEnum.A,
        string? x6 = "str", int? x7 = 42, bool? x8 = true, double? x9 = 0, StringEnum? x10 = StringEnum.A)
    {
        public string X1 { get; } = x1;
        public int X2 { get; } = x2;
        public bool X3 { get; } = x3;
        public double X4 { get; } = x4;
        public StringEnum X5 { get; } = x5;
 
        public string? X6 { get; } = x6;
        public int? X7 { get; } = x7;
        public bool? X8 { get; } = x8;
        public double? X9 { get; } = x9;
        public StringEnum? X10 { get; } = x10;
    }
 
    // Regression test for https://github.com/dotnet/runtime/issues/92487
    public class GenericPocoWithNullableConstructorParameter<T>(T value)
    {
        [NotNull]
        public T Value { get; } = value!;
    }
 
    [JsonDerivedType(typeof(DerivedPocoNoDiscriminator))]
    [JsonDerivedType(typeof(DerivedPocoStringDiscriminator), "derivedPoco")]
    [JsonDerivedType(typeof(DerivedPocoIntDiscriminator), 42)]
    [JsonDerivedType(typeof(DerivedCollection), "derivedCollection")]
    [JsonDerivedType(typeof(DerivedDictionary), "derivedDictionary")]
    public abstract class PocoWithPolymorphism
    {
        public int BaseValue { get; set; }
 
        public class DerivedPocoNoDiscriminator : PocoWithPolymorphism
        {
            public string? DerivedValue { get; set; }
        }
 
        public class DerivedPocoStringDiscriminator : PocoWithPolymorphism
        {
            public string? DerivedValue { get; set; }
        }
 
        public class DerivedPocoIntDiscriminator : PocoWithPolymorphism
        {
            public string? DerivedValue { get; set; }
        }
 
        public class DerivedCollection : PocoWithPolymorphism, IEnumerable<int>
        {
            public IEnumerator<int> GetEnumerator() => Enumerable.Repeat(BaseValue, 1).GetEnumerator();
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
 
        public class DerivedDictionary : PocoWithPolymorphism, IReadOnlyDictionary<string, int>
        {
            public int this[string key] => key == nameof(BaseValue) ? BaseValue : throw new KeyNotFoundException();
            public IEnumerable<string> Keys => [nameof(BaseValue)];
            public IEnumerable<int> Values => [BaseValue];
            public int Count => 1;
            public bool ContainsKey(string key) => key == nameof(BaseValue);
            public bool TryGetValue(string key, out int value) => key == nameof(BaseValue) ? (value = BaseValue) == BaseValue : (value = 0) == 0;
            public IEnumerator<KeyValuePair<string, int>> GetEnumerator() => Enumerable.Repeat(new KeyValuePair<string, int>(nameof(BaseValue), BaseValue), 1).GetEnumerator();
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
    }
 
    [JsonDerivedType(typeof(NonAbstractClassWithSingleDerivedType.Derived))]
    public class NonAbstractClassWithSingleDerivedType
    {
        public class Derived : NonAbstractClassWithSingleDerivedType;
    }
 
    public class PocoCombiningPolymorphicTypeAndDerivedTypes
    {
        public PocoWithPolymorphism PolymorphicValue { get; set; } = new PocoWithPolymorphism.DerivedPocoNoDiscriminator { DerivedValue = "derived" };
        public PocoWithPolymorphism.DerivedPocoNoDiscriminator DerivedValue1 { get; set; } = new() { DerivedValue = "derived" };
        public PocoWithPolymorphism.DerivedPocoStringDiscriminator DerivedValue2 { get; set; } = new() { DerivedValue = "derived" };
    }
 
    public class ClassWithComponentModelAttributes
    {
        public ClassWithComponentModelAttributes(string stringValue, [DefaultValue(42)] int intValue)
        {
            StringValue = stringValue;
            IntValue = intValue;
        }
 
        [RegularExpression(@"\w+")]
        public string StringValue { get; }
 
        public int IntValue { get; }
    }
 
    public class ClassWithOptionalObjectParameter(object? value = null)
    {
        public object? Value { get; } = value;
    }
 
    public readonly struct StructDictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> values)
        : IReadOnlyDictionary<TKey, TValue>
        where TKey : notnull
    {
        private readonly IReadOnlyDictionary<TKey, TValue> _dictionary = values.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        public TValue this[TKey key] => _dictionary[key];
        public IEnumerable<TKey> Keys => _dictionary.Keys;
        public IEnumerable<TValue> Values => _dictionary.Values;
        public int Count => _dictionary.Count;
        public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
#if NETCOREAPP
        public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => _dictionary.TryGetValue(key, out value);
#else
        public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
#endif
        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator();
    }
 
    public class ClassWithPropertiesUsingCustomConverters
    {
        [JsonPropertyOrder(0)]
        public ClassWithCustomConverter1? Prop1 { get; set; }
        [JsonPropertyOrder(1)]
        public ClassWithCustomConverter2? Prop2 { get; set; }
 
        [JsonConverter(typeof(CustomConverter<ClassWithCustomConverter1>))]
        public class ClassWithCustomConverter1;
 
        [JsonConverter(typeof(CustomConverter<ClassWithCustomConverter2>))]
        public class ClassWithCustomConverter2;
 
        public sealed class CustomConverter<T> : JsonConverter<T>
        {
            public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
                => default;
 
            public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
                => writer.WriteNullValue();
        }
    }
 
    [JsonSerializable(typeof(object))]
    [JsonSerializable(typeof(bool))]
    [JsonSerializable(typeof(byte))]
    [JsonSerializable(typeof(ushort))]
    [JsonSerializable(typeof(uint))]
    [JsonSerializable(typeof(ulong))]
    [JsonSerializable(typeof(sbyte))]
    [JsonSerializable(typeof(short))]
    [JsonSerializable(typeof(int))]
    [JsonSerializable(typeof(long))]
    [JsonSerializable(typeof(float))]
    [JsonSerializable(typeof(double))]
    [JsonSerializable(typeof(decimal))]
#if NET7_0_OR_GREATER
    [JsonSerializable(typeof(UInt128))]
    [JsonSerializable(typeof(Int128))]
#endif
#if NET6_0_OR_GREATER
    [JsonSerializable(typeof(Half))]
#endif
    [JsonSerializable(typeof(string))]
    [JsonSerializable(typeof(char))]
    [JsonSerializable(typeof(byte[]))]
    [JsonSerializable(typeof(Memory<byte>))]
    [JsonSerializable(typeof(ReadOnlyMemory<byte>))]
    [JsonSerializable(typeof(DateTime))]
    [JsonSerializable(typeof(DateTimeOffset))]
    [JsonSerializable(typeof(TimeSpan))]
#if NET6_0_OR_GREATER
    [JsonSerializable(typeof(DateOnly))]
    [JsonSerializable(typeof(TimeOnly))]
#endif
    [JsonSerializable(typeof(Guid))]
    [JsonSerializable(typeof(Uri))]
    [JsonSerializable(typeof(Version))]
    [JsonSerializable(typeof(JsonDocument))]
    [JsonSerializable(typeof(JsonElement))]
    [JsonSerializable(typeof(JsonNode))]
    [JsonSerializable(typeof(JsonValue))]
    [JsonSerializable(typeof(JsonObject))]
    [JsonSerializable(typeof(JsonArray))]
    // Enum types
    [JsonSerializable(typeof(IntEnum))]
    [JsonSerializable(typeof(StringEnum))]
    [JsonSerializable(typeof(FlagsStringEnum))]
    // Nullable<T> types
    [JsonSerializable(typeof(bool?))]
    [JsonSerializable(typeof(int?))]
    [JsonSerializable(typeof(double?))]
    [JsonSerializable(typeof(Guid?))]
    [JsonSerializable(typeof(JsonElement?))]
    [JsonSerializable(typeof(IntEnum?))]
    [JsonSerializable(typeof(StringEnum?))]
    [JsonSerializable(typeof(SimpleRecordStruct?))]
    // User-defined POCOs
    [JsonSerializable(typeof(SimplePoco))]
    [JsonSerializable(typeof(SimpleRecord))]
    [JsonSerializable(typeof(SimpleRecordStruct))]
    [JsonSerializable(typeof(RecordWithOptionalParameters))]
    [JsonSerializable(typeof(PocoWithRequiredMembers))]
    [JsonSerializable(typeof(PocoWithIgnoredMembers))]
    [JsonSerializable(typeof(PocoWithCustomNaming))]
    [JsonSerializable(typeof(PocoWithCustomNumberHandling))]
    [JsonSerializable(typeof(PocoWithCustomNumberHandlingOnProperties))]
    [JsonSerializable(typeof(PocoWithRecursiveMembers))]
    [JsonSerializable(typeof(PocoWithNonRecursiveDuplicateOccurrences))]
    [JsonSerializable(typeof(PocoWithDescription))]
    [JsonSerializable(typeof(PocoWithCustomConverter))]
    [JsonSerializable(typeof(PocoWithCustomPropertyConverter))]
    [JsonSerializable(typeof(PocoWithEnums))]
    [JsonSerializable(typeof(PocoWithStructFollowedByNullableStruct))]
    [JsonSerializable(typeof(PocoWithNullableStructFollowedByStruct))]
    [JsonSerializable(typeof(PocoWithExtensionDataProperty))]
    [JsonSerializable(typeof(PocoDisallowingUnmappedMembers))]
    [JsonSerializable(typeof(PocoWithNullableAnnotationAttributes))]
    [JsonSerializable(typeof(PocoWithNullableAnnotationAttributesOnConstructorParams))]
    [JsonSerializable(typeof(PocoWithNullableConstructorParameter))]
    [JsonSerializable(typeof(PocoWithOptionalConstructorParams))]
    [JsonSerializable(typeof(GenericPocoWithNullableConstructorParameter<string>))]
    [JsonSerializable(typeof(PocoWithPolymorphism))]
    [JsonSerializable(typeof(NonAbstractClassWithSingleDerivedType))]
    [JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))]
    [JsonSerializable(typeof(ClassWithComponentModelAttributes))]
    [JsonSerializable(typeof(ClassWithOptionalObjectParameter))]
    [JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))]
    // Collection types
    [JsonSerializable(typeof(int[]))]
    [JsonSerializable(typeof(List<bool>))]
    [JsonSerializable(typeof(HashSet<string>))]
    [JsonSerializable(typeof(Queue<double>))]
    [JsonSerializable(typeof(Stack<char>))]
    [JsonSerializable(typeof(ImmutableArray<int>))]
    [JsonSerializable(typeof(ImmutableList<string>))]
    [JsonSerializable(typeof(ImmutableQueue<bool>))]
    [JsonSerializable(typeof(object[]))]
    [JsonSerializable(typeof(System.Collections.ArrayList))]
    [JsonSerializable(typeof(Dictionary<string, int>))]
    [JsonSerializable(typeof(SortedDictionary<int, string>))]
    [JsonSerializable(typeof(Dictionary<string, SimplePoco>))]
    [JsonSerializable(typeof(Dictionary<string, object>))]
    [JsonSerializable(typeof(Hashtable))]
    [JsonSerializable(typeof(StructDictionary<string, int>))]
    [JsonSerializable(typeof(XElement))]
    public partial class TestTypesContext : JsonSerializerContext;
 
    private static TAttribute? ResolveAttribute<TAttribute>(this JsonSchemaExporterContext ctx)
        where TAttribute : Attribute
    {
        // Resolve attributes from locations in the following order:
        // 1. Property-level attributes
        // 2. Parameter-level attributes and
        // 3. Type-level attributes.
        return
#if NET9_0_OR_GREATER || !TESTS_JSON_SCHEMA_EXPORTER_POLYFILL
            GetAttrs(ctx.PropertyInfo?.AttributeProvider) ??
            GetAttrs(ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider) ??
#else
            GetAttrs(ctx.PropertyAttributeProvider) ??
            GetAttrs(ctx.ParameterInfo) ??
#endif
            GetAttrs(ctx.TypeInfo.Type);
 
        static TAttribute? GetAttrs(ICustomAttributeProvider? provider) =>
            (TAttribute?)provider?.GetCustomAttributes(typeof(TAttribute), inherit: false).FirstOrDefault();
    }
}