JsonSchemaExporter
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
{
[NullableContext(1)]
[Nullable(0)]
public static class JsonSchemaExporter
{
[CompilerFeatureRequired("RefStructs")]
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, [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, [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, [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_;
<>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_);
int num;
Span<string> span;
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);
num = list2.Count;
state.PushSchemaNode(num.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;
}
}
}
JsonSchema obj = new JsonSchema {
Type = jsonSchemaType,
AnyOf = list2
};
object obj2;
if (!flag) {
num = 1;
obj2 = new List<string>(num);
CollectionsMarshal.SetCount((List<string>)obj2, num);
span = CollectionsMarshal.AsSpan((List<string>)obj2);
int num2 = 0;
span[num2] = typeDiscriminatorPropertyName;
num2++;
} else
obj2 = null;
obj.Required = (List<string>)obj2;
return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, obj, ref <>c__DisplayClass2_);
}
}
}
JsonConverter nullableElementConverter = jsonConverter.NullableElementConverter;
if (nullableElementConverter == null) {
Span<KeyValuePair<string, JsonSchema>> span2;
switch (<>c__DisplayClass2_.typeInfo.Kind) {
case JsonTypeInfoKind.Object: {
List<KeyValuePair<string, JsonSchema>> list6 = null;
List<string> list7 = 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();
(list6 ?? (list6 = new List<KeyValuePair<string, JsonSchema>>())).Add(valueOrDefault2);
if (parentPolymorphicTypeContainsTypesWithoutDiscriminator)
(list7 ?? (list7 = 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;
}
(list6 ?? (list6 = new List<KeyValuePair<string, JsonSchema>>())).Add(new KeyValuePair<string, JsonSchema>(property.Name, jsonSchema5));
if (property == null)
goto IL_05b3;
if (!property.IsRequired) {
JsonParameterInfo associatedParameter2 = property.AssociatedParameter;
if (associatedParameter2 == null || !associatedParameter2.IsRequiredParameter)
goto IL_05b3;
}
bool parentPolymorphicTypeIsNonNullable2 = true;
goto IL_05b6;
IL_05b3:
parentPolymorphicTypeIsNonNullable2 = false;
goto IL_05b6;
IL_05b6:
if (parentPolymorphicTypeIsNonNullable2)
(list7 ?? (list7 = new List<string>())).Add(property.Name);
}
state.PopSchemaNode();
return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, new JsonSchema {
Type = JsonSchemaType.Object,
Properties = list6,
Required = list7,
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();
JsonSchema obj3 = new JsonSchema {
Type = JsonSchemaType.Object
};
int num2 = 2;
List<KeyValuePair<string, JsonSchema>> list5 = new List<KeyValuePair<string, JsonSchema>>(num2);
CollectionsMarshal.SetCount(list5, num2);
span2 = CollectionsMarshal.AsSpan(list5);
num = 0;
span2[num] = typeDiscriminator.Value;
num++;
span2[num] = new KeyValuePair<string, JsonSchema>("$values", new JsonSchema {
Type = JsonSchemaType.Array,
Items = (jsonSchema4.IsTrue ? null : jsonSchema4)
});
num++;
obj3.Properties = list5;
object obj4;
if (!parentPolymorphicTypeContainsTypesWithoutDiscriminator)
obj4 = null;
else {
num = 1;
obj4 = new List<string>(num);
CollectionsMarshal.SetCount((List<string>)obj4, num);
span = CollectionsMarshal.AsSpan((List<string>)obj4);
num2 = 0;
span[num2] = typeDiscriminator.Value.Key;
num2++;
}
obj3.Required = (List<string>)obj4;
return <MapJsonSchemaCore>g__CompleteSchema|2_0(ref state, obj3, 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();
int num2 = 1;
List<KeyValuePair<string, JsonSchema>> list3 = new List<KeyValuePair<string, JsonSchema>>(num2);
CollectionsMarshal.SetCount(list3, num2);
span2 = CollectionsMarshal.AsSpan(list3);
num = 0;
span2[num] = valueOrDefault;
num++;
properties = list3;
if (parentPolymorphicTypeContainsTypesWithoutDiscriminator) {
num = 1;
List<string> list4 = new List<string>(num);
CollectionsMarshal.SetCount(list4, num);
span = CollectionsMarshal.AsSpan(list4);
num2 = 0;
span[num2] = valueOrDefault.Key;
num2++;
required = list4;
}
}
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;
}
}
}