<PackageReference Include="System.Text.Json" Version="10.0.0-preview.2.25163.2" />

JsonSchemaExporter

public static class JsonSchemaExporter
Functionality for exporting JSON schema from serialization contracts defined in JsonTypeInfo.
using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Schema { [System.Runtime.CompilerServices.NullableContext(1)] [System.Runtime.CompilerServices.Nullable(0)] public static class JsonSchemaExporter { private readonly ref struct GenerationState { private readonly List<string> _currentPath; private readonly Dictionary<(JsonTypeInfo, JsonPropertyInfo), string[]> _generated; public int CurrentDepth => _currentPath.Count; public JsonSerializerOptions Options { get; } public JsonSchemaExporterOptions ExporterOptions { get; } public GenerationState(JsonSerializerOptions options, JsonSchemaExporterOptions exporterOptions) { _currentPath = new List<string>(); _generated = new Dictionary<(JsonTypeInfo, JsonPropertyInfo), string[]>(); Options = options; ExporterOptions = exporterOptions; } public void PushSchemaNode(string nodeId) { if (CurrentDepth == Options.EffectiveMaxDepth) ThrowHelper.ThrowInvalidOperationException_JsonSchemaExporterDepthTooLarge(); _currentPath.Add(nodeId); } public void PopSchemaNode() { _currentPath.RemoveAt(_currentPath.Count - 1); } public bool TryGetExistingJsonPointer([In] [System.Runtime.CompilerServices.IsReadOnly] ref JsonSchemaExporterContext context, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string existingJsonPointer) { (JsonTypeInfo, JsonPropertyInfo) key = (context.TypeInfo, context.PropertyInfo); if (_generated.TryGetValue(key, out string[] value)) { existingJsonPointer = FormatJsonPointer(value); return true; } _generated[key] = context._path; existingJsonPointer = null; return false; } public JsonSchemaExporterContext CreateContext(JsonTypeInfo typeInfo, JsonPropertyInfo propertyInfo, JsonTypeInfo baseTypeInfo) { return new JsonSchemaExporterContext(typeInfo, propertyInfo, baseTypeInfo, _currentPath.ToArray()); } private static string FormatJsonPointer(ReadOnlySpan<string> path) { if (!path.IsEmpty) { System.Text.ValueStringBuilder valueStringBuilder = new System.Text.ValueStringBuilder(path.Length * 10); try { valueStringBuilder.Append('#'); ReadOnlySpan<string> readOnlySpan = path; for (int i = 0; i < readOnlySpan.Length; i++) { ReadOnlySpan<char> readOnlySpan2 = readOnlySpan[i].AsSpan(); valueStringBuilder.Append('/'); do { int num = readOnlySpan2.IndexOfAny('~', '/'); if (num < 0) { valueStringBuilder.Append(readOnlySpan2); break; } valueStringBuilder.Append(readOnlySpan2.Slice(0, num)); if (readOnlySpan2[num] == '~') valueStringBuilder.Append("~0"); else valueStringBuilder.Append("~1"); readOnlySpan2 = readOnlySpan2.Slice(num + 1); } while (!readOnlySpan2.IsEmpty); } return valueStringBuilder.ToString(); } finally { valueStringBuilder.Dispose(); } } return "#"; } } public static JsonNode GetJsonSchemaAsNode(this JsonSerializerOptions options, Type type, [System.Runtime.CompilerServices.Nullable(2)] JsonSchemaExporterOptions exporterOptions = null) { if (options == null) ThrowHelper.ThrowArgumentNullException("options"); if ((object)type == null) ThrowHelper.ThrowArgumentNullException("type"); ValidateOptions(options); return options.GetTypeInfoInternal(type, true, true, false, false).GetJsonSchemaAsNode(exporterOptions); } public static JsonNode GetJsonSchemaAsNode(this JsonTypeInfo typeInfo, [System.Runtime.CompilerServices.Nullable(2)] JsonSchemaExporterOptions exporterOptions = null) { if (typeInfo == null) ThrowHelper.ThrowArgumentNullException("typeInfo"); ValidateOptions(typeInfo.Options); if (exporterOptions == null) exporterOptions = JsonSchemaExporterOptions.Default; typeInfo.EnsureConfigured(); GenerationState state = new GenerationState(typeInfo.Options, exporterOptions); return MapJsonSchemaCore(ref state, typeInfo, null, null, null, null, false, false, null, true).ToJsonNode(exporterOptions); } private static JsonSchema MapJsonSchemaCore(ref GenerationState state, JsonTypeInfo typeInfo, JsonPropertyInfo propertyInfo = null, JsonConverter customConverter = null, JsonNumberHandling? customNumberHandling = default(JsonNumberHandling?), JsonTypeInfo parentPolymorphicTypeInfo = null, bool parentPolymorphicTypeContainsTypesWithoutDiscriminator = false, bool parentPolymorphicTypeIsNonNullable = false, KeyValuePair<string, JsonSchema>? typeDiscriminator = default(KeyValuePair<string, JsonSchema>?), bool cacheResult = true) { <>c__DisplayClass2_0 <>c__DisplayClass2_ = default(<>c__DisplayClass2_0); <>c__DisplayClass2_.propertyInfo = propertyInfo; <>c__DisplayClass2_.typeInfo = typeInfo; <>c__DisplayClass2_.parentPolymorphicTypeIsNonNullable = parentPolymorphicTypeIsNonNullable; <>c__DisplayClass2_.exporterContext = state.CreateContext(<>c__DisplayClass2_.typeInfo, <>c__DisplayClass2_.propertyInfo, parentPolymorphicTypeInfo); if (cacheResult && <>c__DisplayClass2_.typeInfo.Kind != 0 && state.TryGetExistingJsonPointer(ref <>c__DisplayClass2_.exporterContext, out string existingJsonPointer)) return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema { Ref = existingJsonPointer }, ref <>c__DisplayClass2_); JsonConverter jsonConverter = customConverter ?? <>c__DisplayClass2_.typeInfo.Converter; JsonNumberHandling jsonNumberHandling = customNumberHandling ?? <>c__DisplayClass2_.typeInfo.NumberHandling ?? <>c__DisplayClass2_.typeInfo.Options.NumberHandling; JsonSchema schema = jsonConverter.GetSchema(jsonNumberHandling); if (schema != null) return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, schema, ref <>c__DisplayClass2_); if (parentPolymorphicTypeInfo == null) { JsonPolymorphismOptions polymorphismOptions = <>c__DisplayClass2_.typeInfo.PolymorphismOptions; if (polymorphismOptions != null) { IList<JsonDerivedType> derivedTypes2 = polymorphismOptions.DerivedTypes; if (derivedTypes2 != null && derivedTypes2.Count > 0) { string typeDiscriminatorPropertyName = polymorphismOptions.TypeDiscriminatorPropertyName; List<JsonDerivedType> list = new List<JsonDerivedType>(polymorphismOptions.DerivedTypes); if (!<>c__DisplayClass2_.typeInfo.Type.IsAbstract && !IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(<>c__DisplayClass2_.typeInfo)) list.Add(new JsonDerivedType(<>c__DisplayClass2_.typeInfo.Type)); bool flag = list.Exists((JsonDerivedType derivedTypes) => derivedTypes.TypeDiscriminator == null); JsonSchemaType jsonSchemaType = JsonSchemaType.Any; List<JsonSchema> list2 = new List<JsonSchema>(list.Count); state.PushSchemaNode("anyOf"); foreach (JsonDerivedType item in list) { KeyValuePair<string, JsonSchema>? nullable = null; object typeDiscriminator2 = item.TypeDiscriminator; if (typeDiscriminator2 != null) { string text = typeDiscriminator2 as string; JsonNode jsonNode = (text == null) ? ((JsonNode)(int)typeDiscriminator2) : ((JsonNode)text); JsonNode constant = jsonNode; JsonSchema value = new JsonSchema { Constant = constant }; nullable = new KeyValuePair<string, JsonSchema>(typeDiscriminatorPropertyName, value); } JsonTypeInfo typeInfoInternal = <>c__DisplayClass2_.typeInfo.Options.GetTypeInfoInternal(item.DerivedType, true, true, false, false); state.PushSchemaNode(list2.Count.ToString(CultureInfo.InvariantCulture)); JsonTypeInfo typeInfo2 = typeInfoInternal; JsonTypeInfo typeInfo3 = <>c__DisplayClass2_.typeInfo; KeyValuePair<string, JsonSchema>? typeDiscriminator3 = nullable; bool parentPolymorphicTypeContainsTypesWithoutDiscriminator2 = flag; bool parentPolymorphicTypeIsNonNullable2 = <>c__DisplayClass2_.propertyInfo != null && !<>c__DisplayClass2_.propertyInfo.IsGetNullable && !<>c__DisplayClass2_.propertyInfo.IsSetNullable; JsonSchema jsonSchema = MapJsonSchemaCore(ref state, typeInfo2, null, null, null, typeInfo3, parentPolymorphicTypeContainsTypesWithoutDiscriminator2, parentPolymorphicTypeIsNonNullable2, typeDiscriminator3, false); state.PopSchemaNode(); if (list2.Count == 0) jsonSchemaType = jsonSchema.Type; else if (jsonSchemaType != jsonSchema.Type) { jsonSchemaType = JsonSchemaType.Any; } list2.Add(jsonSchema); } state.PopSchemaNode(); if (jsonSchemaType != 0) { foreach (JsonSchema item2 in list2) { item2.Type = JsonSchemaType.Any; if (item2.KeywordCount == 0) { list2 = null; break; } } } return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema { Type = jsonSchemaType, AnyOf = list2, Required = (flag ? null : new List<string>(1) { typeDiscriminatorPropertyName }) }, ref <>c__DisplayClass2_); } } } JsonConverter nullableElementConverter = jsonConverter.NullableElementConverter; if (nullableElementConverter == null) { switch (<>c__DisplayClass2_.typeInfo.Kind) { case JsonTypeInfoKind.Object: { List<KeyValuePair<string, JsonSchema>> list3 = null; List<string> list4 = null; JsonSchema additionalProperties = null; if ((<>c__DisplayClass2_.typeInfo.UnmappedMemberHandling ?? <>c__DisplayClass2_.typeInfo.Options.UnmappedMemberHandling) == JsonUnmappedMemberHandling.Disallow) additionalProperties = JsonSchema.CreateFalseSchema(); if (typeDiscriminator.HasValue) { KeyValuePair<string, JsonSchema> valueOrDefault2 = typeDiscriminator.GetValueOrDefault(); (list3 ?? (list3 = new List<KeyValuePair<string, JsonSchema>>())).Add(valueOrDefault2); if (parentPolymorphicTypeContainsTypesWithoutDiscriminator) (list4 ?? (list4 = new List<string>())).Add(valueOrDefault2.Key); } state.PushSchemaNode("properties"); foreach (JsonPropertyInfo property in <>c__DisplayClass2_.typeInfo.Properties) { if ((property != null && ((property.Get == null && property.Set == null) || property.IsExtensionData)) ? true : false) continue; state.PushSchemaNode(property.Name); JsonSchema schema2 = MapJsonSchemaCore(ref state, property.JsonTypeInfo, property, property.EffectiveConverter, property.EffectiveNumberHandling, null, false, false, null, true); state.PopSchemaNode(); JsonParameterInfo associatedParameter = property.AssociatedParameter; if (associatedParameter != null && associatedParameter.HasDefaultValue) { JsonSchema.EnsureMutable(ref schema2); schema2.DefaultValue = JsonSerializer.SerializeToNode(associatedParameter.DefaultValue, property.JsonTypeInfo); schema2.HasDefaultValue = true; } (list3 ?? (list3 = new List<KeyValuePair<string, JsonSchema>>())).Add(new KeyValuePair<string, JsonSchema>(property.Name, schema2)); if (property == null) goto IL_05b4; if (!property.IsRequired) { JsonParameterInfo associatedParameter2 = property.AssociatedParameter; if (associatedParameter2 == null || !associatedParameter2.IsRequiredParameter) goto IL_05b4; } bool parentPolymorphicTypeIsNonNullable2 = true; goto IL_05b7; IL_05b4: parentPolymorphicTypeIsNonNullable2 = false; goto IL_05b7; IL_05b7: if (parentPolymorphicTypeIsNonNullable2) (list4 ?? (list4 = new List<string>())).Add(property.Name); } state.PopSchemaNode(); return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema { Type = JsonSchemaType.Object, Properties = list3, Required = list4, AdditionalProperties = additionalProperties }, ref <>c__DisplayClass2_); } case JsonTypeInfoKind.Enumerable: { if (!typeDiscriminator.HasValue) { state.PushSchemaNode("items"); JsonSchema jsonSchema3 = MapJsonSchemaCore(ref state, <>c__DisplayClass2_.typeInfo.ElementTypeInfo, null, null, jsonNumberHandling, null, false, false, null, true); state.PopSchemaNode(); return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema { Type = JsonSchemaType.Array, Items = (jsonSchema3.IsTrue ? null : jsonSchema3) }, ref <>c__DisplayClass2_); } state.PushSchemaNode("properties"); state.PushSchemaNode("$values"); state.PushSchemaNode("items"); JsonSchema jsonSchema4 = MapJsonSchemaCore(ref state, <>c__DisplayClass2_.typeInfo.ElementTypeInfo, null, null, jsonNumberHandling, null, false, false, null, true); state.PopSchemaNode(); state.PopSchemaNode(); state.PopSchemaNode(); return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema { Type = JsonSchemaType.Object, Properties = new List<KeyValuePair<string, JsonSchema>>(2) { typeDiscriminator.Value, new KeyValuePair<string, JsonSchema>("$values", new JsonSchema { Type = JsonSchemaType.Array, Items = (jsonSchema4.IsTrue ? null : jsonSchema4) }) }, Required = (parentPolymorphicTypeContainsTypesWithoutDiscriminator ? new List<string>(1) { typeDiscriminator.Value.Key } : null) }, ref <>c__DisplayClass2_); } case JsonTypeInfoKind.Dictionary: { List<KeyValuePair<string, JsonSchema>> properties = null; List<string> required = null; if (typeDiscriminator.HasValue) { KeyValuePair<string, JsonSchema> valueOrDefault = typeDiscriminator.GetValueOrDefault(); properties = new List<KeyValuePair<string, JsonSchema>>(1) { valueOrDefault }; if (parentPolymorphicTypeContainsTypesWithoutDiscriminator) required = new List<string>(1) { valueOrDefault.Key }; } state.PushSchemaNode("additionalProperties"); JsonSchema jsonSchema2 = MapJsonSchemaCore(ref state, <>c__DisplayClass2_.typeInfo.ElementTypeInfo, null, null, jsonNumberHandling, null, false, false, null, true); state.PopSchemaNode(); return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema { Type = JsonSchemaType.Object, Properties = properties, Required = required, AdditionalProperties = (jsonSchema2.IsTrue ? null : jsonSchema2) }, ref <>c__DisplayClass2_); } default: return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, JsonSchema.CreateTrueSchema(), ref <>c__DisplayClass2_); } } JsonTypeInfo typeInfo4 = <>c__DisplayClass2_.typeInfo.Options.GetTypeInfo(nullableElementConverter.Type); schema = MapJsonSchemaCore(ref state, typeInfo4, null, nullableElementConverter, null, null, false, false, null, false); if (schema.Enum != null) schema.Enum.Add(null); return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, schema, ref <>c__DisplayClass2_); } private static void ValidateOptions(JsonSerializerOptions options) { if (options.ReferenceHandler == ReferenceHandler.Preserve) ThrowHelper.ThrowNotSupportedException_JsonSchemaExporterDoesNotSupportReferenceHandlerPreserve(); options.MakeReadOnly(); } private static bool IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(JsonTypeInfo typeInfo) { foreach (JsonDerivedType derivedType in typeInfo.PolymorphismOptions.DerivedTypes) { if (derivedType.DerivedType == typeInfo.Type) return true; } return false; } } }