Поделиться через


Экспорт схемы JSON

Класс, представленный JsonSchemaExporter в .NET 9, позволяет извлекать документы схемы JSON из типов .NET с помощью либо экземпляра JsonSerializerOptions JsonTypeInfo . Результирующая схема предоставляет спецификацию контракта сериализации JSON для типа .NET. Схема описывает форму сериализации и десериализации.

Фрагмент кода приведен ниже.

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);

Как видно в этом примере, экспортер отличается от свойств, допускающих значение NULL, и заполняет required ключевое слово в соответствии с параметром конструктора, необязательным или нет.

Настройка выходных данных схемы

Вы можете повлиять на выходные данные схемы по конфигурации, указанной в JsonSerializerOptions вызываемом JsonTypeInfo методе GetJsonSchemaAsNode . В следующем примере политика именования KebabCaseUpperзадает значение , записывает числа в виде строк и запрещает незамеченные свойства.

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; }
}

Вы можете дополнительно контролировать созданную схему с помощью JsonSchemaExporterOptions типа конфигурации. В следующем примере свойство true помечает TreatNullObliviousAsNonNullable типы корневого уровня как не допускающие значения NULL.

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);

Преобразование созданной схемы

Вы можете применить собственные преобразования к созданным узлам схемы, указав TransformSchemaNode делегат. В следующем примере текст из DescriptionAttribute заметок включается в созданную схему.

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;
    }
};

В следующем примере кода создается схема, которая включает description источник ключевых слов из DescriptionAttribute заметок:

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);