Freigeben über


JSON-Schema-Exporter

Mit der JsonSchemaExporter-Klasse, die in .NET 9 eingeführt wurde, können Sie JSON-Schema Dokumente aus .NET-Typen extrahieren, indem Sie entweder eine JsonSerializerOptions- oder JsonTypeInfo-Instanz verwenden. Das resultierende Schema enthält eine Spezifikation des JSON-Serialisierungsvertrags für den .NET-Typ. Das Schema beschreibt die Form dessen, was serialisiert werden soll und was deserialisiert werden kann.

Der folgende Codeausschnitt zeigt ein Beispiel.

public static void SimpleExtraction()
{
    JsonSerializerOptions options = JsonSerializerOptions.Default;
    JsonNode schema = options.GetJsonSchemaAsNode(typeof(Person));
    Console.WriteLine(schema.ToString());
    //{
    //  "type": ["object", "null"],
    //  "properties": {
    //    "Name": { "type": "string" },
    //    "Age": { "type": "integer" },
    //    "Address": { "type": ["string", "null"], "default": null }
    //  },
    //  "required": ["Name", "Age"]
    //}
}

record Person(string Name, int Age, string? Address = null);

Wie in diesem Beispiel zu sehen ist, unterscheidet der Exporter zwischen nullablen und nicht nullablen Eigenschaften und füllt das required Schlüsselwort anhand eines Konstruktorparameters, der optional ist oder nicht.

Konfigurieren der Schemaausgabe

Sie können die Schemaausgabe durch die Konfiguration beeinflussen, die in der JsonSerializerOptions oder JsonTypeInfo-Instanz angegeben ist, für die Sie die GetJsonSchemaAsNode-Methode aufrufen. Das folgende Beispiel legt die Benennungsrichtlinie auf KebabCaseUpper, schreibt Zahlen als Zeichenfolgen und verbietet nicht zugeordnete Eigenschaften.

public static void CustomExtraction()
{
    JsonSerializerOptions options = new(JsonSerializerOptions.Default)
    {
        PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper,
        NumberHandling = JsonNumberHandling.WriteAsString,
        UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
    };

    JsonNode schema = options.GetJsonSchemaAsNode(typeof(MyPoco));
    Console.WriteLine(schema.ToString());
    //{
    //  "type": ["object", "null"],
    //  "properties": {
    //    "NUMERIC-VALUE": {
    //      "type": ["string", "integer"],
    //      "pattern": "^-?(?:0|[1-9]\\d*)$"
    //    }
    //  },
    //  "additionalProperties": false
    //}
}

class MyPoco
{
    public int NumericValue { get; init; }
}

Sie können das generierte Schema mithilfe des JsonSchemaExporterOptions Konfigurationstyps weiter steuern. Im folgenden Beispiel wird die TreatNullObliviousAsNonNullable-Eigenschaft so festgelegt, dass true-Typen auf Stammebene als nicht nullwertebar markiert werden.

public static void CustomExtraction()
{
    JsonSerializerOptions options = JsonSerializerOptions.Default;
    JsonSchemaExporterOptions exporterOptions = new()
    {
        TreatNullObliviousAsNonNullable = true,
    };

    JsonNode schema = options.GetJsonSchemaAsNode(typeof(Person), exporterOptions);
    Console.WriteLine(schema.ToString());
    //{
    //  "type": "object",
    //  "properties": {
    //    "Name": { "type": "string" }
    //  },
    //  "required": ["Name"]
    //}
}

record Person(string Name);

Transformieren des generierten Schemas

Sie können ihre eigenen Transformationen auf generierte Schemaknoten anwenden, indem Sie einen TransformSchemaNode Delegaten angeben. Im folgenden Beispiel werden Text aus DescriptionAttribute Anmerkungen in das generierte Schema integriert.

JsonSchemaExporterOptions exporterOptions = new()
{
    TransformSchemaNode = (context, schema) =>
    {
        // Determine if a type or property and extract the relevant attribute provider.
        ICustomAttributeProvider? attributeProvider = context.PropertyInfo is not null
            ? context.PropertyInfo.AttributeProvider
            : context.TypeInfo.Type;

        // Look up any description attributes.
        DescriptionAttribute? descriptionAttr = attributeProvider?
            .GetCustomAttributes(inherit: true)
            .Select(attr => attr as DescriptionAttribute)
            .FirstOrDefault(attr => attr is not null);

        // Apply description attribute to the generated schema.
        if (descriptionAttr != null)
        {
            if (schema is not JsonObject jObj)
            {
                // Handle the case where the schema is a Boolean.
                JsonValueKind valueKind = schema.GetValueKind();
                Debug.Assert(valueKind is JsonValueKind.True or JsonValueKind.False);
                schema = jObj = new JsonObject();
                if (valueKind is JsonValueKind.False)
                {
                    jObj.Add("not", true);
                }
            }

            jObj.Insert(0, "description", descriptionAttr.Description);
        }

        return schema;
    }
};

Das folgende Codebeispiel generiert ein Schema, das die description Schlüsselwortquelle aus DescriptionAttribute-Annotationen einbezieht:

JsonSerializerOptions options = JsonSerializerOptions.Default;
JsonNode schema = options.GetJsonSchemaAsNode(typeof(Person), exporterOptions);
Console.WriteLine(schema.ToString());
//{
//  "description": "A person",
//  "type": ["object", "null"],
//  "properties": {
//    "Name": { "description": "The name of the person", "type": "string" }
//  },
//  "required": ["Name"]
//}
[Description("A person")]
record Person([property: Description("The name of the person")] string Name);