Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
La JsonSchemaExporter classe, introdotta in .NET 9, consente di estrarre documenti dello schema JSON dai tipi .NET usando un'istanza JsonSerializerOptions o JsonTypeInfo . Lo schema risultante fornisce una specifica del contratto di serializzazione JSON per il tipo .NET. Lo schema descrive la forma di ciò che verrebbe serializzato e di cosa può essere deserializzato.
Il frammento di codice seguente mostra un esempio.
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);
Come si può notare in questo esempio, l'esportatore distingue tra proprietà nullable e non-nullable e popola la parola chiave required a seconda se un parametro del costruttore è facoltativo o meno.
A partire da .NET 11, l'esportatore riconosce i tipi BFloat16, Decimal32, Decimal64 e Decimal128. Esporta gli schemi per le relative forme nullable e per i letterali con nome quando si attiva AllowNamedFloatingPointLiterals.
Schemi per i tipi di unione
A partire da .NET 11, JsonSchemaExporter descrive una C# union con uno schema non contrassegnato anyOf che presenta un ramo per ogni caso. L'unione non aggiunge alcuna discriminante perché JsonSerializer scrive solo la variante attiva. Al contrario, quando si abilita l'inferenza per una gerarchia chiusa, JSON serializzato come tipo di base chiuso include un $type discriminatorio che identifica il tipo derivato. Il JsonSchemaExporter descritto qui è anyOf l'output; ASP.NET Core genera i documenti OpenAPI separatamente.
Configurazione dell'output dello schema
È possibile influenzare l'output dello schema configurando l'istanza JsonSerializerOptions o JsonTypeInfo su cui si chiama il metodo GetJsonSchemaAsNode. L'esempio seguente imposta i criteri di denominazione su KebabCaseUpper, scrive i numeri come stringhe e non consente le proprietà non mappate.
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; }
}
È possibile controllare ulteriormente lo schema generato usando il tipo di configurazione JsonSchemaExporterOptions. L'esempio seguente imposta la proprietà TreatNullObliviousAsNonNullable su true per contrassegnare i tipi a livello di radice che non ammettono valori 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);
Trasformazione dello schema generato
È possibile applicare proprie trasformazioni ai nodi dello schema generati specificando un delegato TransformSchemaNode. L'esempio seguente incorpora il testo delle annotazioni DescriptionAttribute nello schema generato.
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;
}
};
L'esempio di codice seguente genera uno schema che incorpora l'origine delle parole chiave description dalle annotazioni 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);