JSON スキーマ エクスポーター
.NET 9 で導入された新しい JsonSchemaExporter クラスを使用すると、JsonSerializerOptions または JsonTypeInfo インスタンスを使用して、.NET 型から JSON スキーマ ドキュメントを抽出できます。 結果として得られるスキーマは、.NET 型の JSON シリアル化コントラクトの仕様です。 スキーマには、シリアル化される内容と逆シリアル化できる内容の形式が記述されています。
次のコード スニペットに例を示します。
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 許容プロパティと null 非許容プロパティを区別し、コンストラクター パラメーターがオプションであるかどうかに応じて required
キーワードを設定します。
スキーマ出力を構成する
GetJsonSchemaAsNode メソッドを呼び出す JsonSerializerOptions または JsonTypeInfo インスタンスで指定された構成によって、スキーマ出力に影響を与えることができます。 次の例では、命名ポリシーを 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 型の構成を使用すると、生成されたスキーマをさらに制御できます。 次の例では、TreatNullObliviousAsNonNullable プロパティを true
に設定することで、ルートレベルの型を 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;
}
};
次のコード例では、DescriptionAttribute 注釈から description
キーワード ソースを組み込むスキーマを生成しています。
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);
.NET