<PackageReference Include="System.Text.Json" Version="9.0.0-rc.1.24431.7" />

JsonSchemaExporter

public static class JsonSchemaExporter
using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; 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 List<(JsonTypeInfo typeInfo, JsonPropertyInfo propertyInfo, int depth)> _generationStack; public int CurrentDepth => _currentPath.Count; public JsonSerializerOptions Options { get; } public JsonSchemaExporterOptions ExporterOptions { get; } public GenerationState(JsonSerializerOptions options, JsonSchemaExporterOptions exporterOptions) { _currentPath = new List<string>(); _generationStack = new List<(JsonTypeInfo, JsonPropertyInfo, int)>(); 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 TryPushType(JsonTypeInfo typeInfo, JsonPropertyInfo propertyInfo, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string existingJsonPointer) { foreach ((JsonTypeInfo, JsonPropertyInfo, int) item4 in _generationStack) { JsonTypeInfo item = item4.Item1; JsonPropertyInfo item2 = item4.Item2; int item3 = item4.Item3; if (typeInfo == item && propertyInfo == item2) { existingJsonPointer = FormatJsonPointer(_currentPath, item3); return true; } } _generationStack.Add((typeInfo, propertyInfo, CurrentDepth)); existingJsonPointer = null; return false; } public void PopGeneratedType() { _generationStack.RemoveAt(_generationStack.Count - 1); } public JsonSchemaExporterContext CreateContext(JsonTypeInfo typeInfo, JsonPropertyInfo propertyInfo, JsonTypeInfo baseTypeInfo) { return new JsonSchemaExporterContext(typeInfo, propertyInfo, baseTypeInfo, _currentPath.ToArray()); } private static string FormatJsonPointer(List<string> currentPathList, int depth) { if (depth != 0) { System.Text.ValueStringBuilder valueStringBuilder = new System.Text.ValueStringBuilder(depth * 10); try { valueStringBuilder.Append('#'); for (int i = 0; i < depth; i++) { ReadOnlySpan<char> readOnlySpan = currentPathList[i].AsSpan(); valueStringBuilder.Append('/'); do { int num = readOnlySpan.IndexOfAny('~', '/'); if (num < 0) { valueStringBuilder.Append(readOnlySpan); break; } valueStringBuilder.Append(readOnlySpan.Slice(0, num)); if (readOnlySpan[num] == '~') valueStringBuilder.Append("~0"); else valueStringBuilder.Append("~1"); readOnlySpan = readOnlySpan.Slice(num + 1); } while (!readOnlySpan.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_.cacheResult = cacheResult; <>c__DisplayClass2_.parentPolymorphicTypeInfo = parentPolymorphicTypeInfo; if (<>c__DisplayClass2_.cacheResult && state.TryPushType(<>c__DisplayClass2_.typeInfo, <>c__DisplayClass2_.propertyInfo, 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 (<>c__DisplayClass2_.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; JsonUnmappedMemberHandling? unmappedMemberHandling = <>c__DisplayClass2_.typeInfo.UnmappedMemberHandling; if (unmappedMemberHandling.HasValue && unmappedMemberHandling.GetValueOrDefault() == JsonUnmappedMemberHandling.Disallow) additionalProperties = JsonSchema.False; 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 jsonSchema5 = 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) { jsonSchema5.DefaultValue = JsonSerializer.SerializeToNode(associatedParameter.DefaultValue, property.JsonTypeInfo); jsonSchema5.HasDefaultValue = true; } (list3 ?? (list3 = new List<KeyValuePair<string, JsonSchema>>())).Add(new KeyValuePair<string, JsonSchema>(property.Name, jsonSchema5)); if (property == null) goto IL_0592; if (!property.IsRequired) { JsonParameterInfo associatedParameter2 = property.AssociatedParameter; if (associatedParameter2 == null || !associatedParameter2.IsRequiredParameter) goto IL_0592; } bool parentPolymorphicTypeIsNonNullable2 = true; goto IL_0595; IL_0592: parentPolymorphicTypeIsNonNullable2 = false; goto IL_0595; IL_0595: 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.True, 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; } } }