< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 377
Coverable lines: 377
Total lines: 610
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 197
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Globalization;
 8using System.Reflection;
 9using System.Runtime.InteropServices;
 10using System.Text.Json.Nodes;
 11using System.Text.Json.Serialization;
 12using System.Text.Json.Serialization.Metadata;
 13
 14namespace System.Text.Json.Schema
 15{
 16    /// <summary>
 17    /// Functionality for exporting JSON schema from serialization contracts defined in <see cref="JsonTypeInfo"/>.
 18    /// </summary>
 19    public static class JsonSchemaExporter
 20    {
 21        /// <summary>
 22        /// Gets the JSON schema for <paramref name="type"/> as a <see cref="JsonNode"/> document.
 23        /// </summary>
 24        /// <param name="options">The options declaring the contract for the type.</param>
 25        /// <param name="type">The type for which to resolve a schema.</param>
 26        /// <param name="exporterOptions">The options object governing the export operation.</param>
 27        /// <returns>A JSON object containing the schema for <paramref name="type"/>.</returns>
 28        public static JsonNode GetJsonSchemaAsNode(this JsonSerializerOptions options, Type type, JsonSchemaExporterOpti
 029        {
 030            ArgumentNullException.ThrowIfNull(options);
 031            ArgumentNullException.ThrowIfNull(type);
 32
 033            ValidateOptions(options);
 034            JsonTypeInfo typeInfo = options.GetTypeInfoInternal(type);
 035            return typeInfo.GetJsonSchemaAsNode(exporterOptions);
 036        }
 37
 38        /// <summary>
 39        /// Gets the JSON schema for <paramref name="typeInfo"/> as a <see cref="JsonNode"/> document.
 40        /// </summary>
 41        /// <param name="typeInfo">The contract from which to resolve the JSON schema.</param>
 42        /// <param name="exporterOptions">The options object governing the export operation.</param>
 43        /// <returns>A JSON object containing the schema for <paramref name="typeInfo"/>.</returns>
 44        public static JsonNode GetJsonSchemaAsNode(this JsonTypeInfo typeInfo, JsonSchemaExporterOptions? exporterOption
 045        {
 046            ArgumentNullException.ThrowIfNull(typeInfo);
 47
 048            ValidateOptions(typeInfo.Options);
 049            exporterOptions ??= JsonSchemaExporterOptions.Default;
 50
 051            typeInfo.EnsureConfigured();
 052            GenerationState state = new(typeInfo.Options, exporterOptions);
 053            JsonSchema schema = MapJsonSchemaCore(ref state, typeInfo);
 054            return schema.ToJsonNode(exporterOptions);
 055        }
 56
 57        private static JsonSchema MapJsonSchemaCore(
 58            ref GenerationState state,
 59            JsonTypeInfo typeInfo,
 60            JsonPropertyInfo? propertyInfo = null,
 61            JsonConverter? customConverter = null,
 62            JsonNumberHandling? customNumberHandling = null,
 63            JsonTypeInfo? parentPolymorphicTypeInfo = null,
 64            bool parentPolymorphicTypeContainsTypesWithoutDiscriminator = false,
 65            bool parentPolymorphicTypeIsNonNullable = false,
 66            KeyValuePair<string, JsonSchema>? typeDiscriminator = null,
 67            bool cacheResult = true)
 068        {
 069            Debug.Assert(typeInfo.IsConfigured);
 70
 071            JsonSchemaExporterContext exporterContext = state.CreateContext(typeInfo, propertyInfo, parentPolymorphicTyp
 72
 073            if (cacheResult && typeInfo.Kind is not JsonTypeInfoKind.None &&
 074                state.TryGetExistingJsonPointer(exporterContext, out string? existingJsonPointer))
 075            {
 76                // The schema context has already been generated in the schema document, return a reference to it.
 077                return CompleteSchema(ref state, new JsonSchema { Ref = existingJsonPointer });
 78            }
 79
 080            JsonConverter effectiveConverter = customConverter ?? typeInfo.Converter;
 081            JsonNumberHandling effectiveNumberHandling = customNumberHandling ?? typeInfo.NumberHandling ?? typeInfo.Opt
 082            if (effectiveConverter.GetSchema(effectiveNumberHandling) is { } schema)
 083            {
 84                // A schema has been provided by the converter.
 085                return CompleteSchema(ref state, schema);
 86            }
 87
 088            if (parentPolymorphicTypeInfo is null && typeInfo.PolymorphismOptions is { DerivedTypes.Count: > 0 } polyOpt
 089            {
 90                // This is the base type of a polymorphic type hierarchy. The schema for this type
 91                // will include an "anyOf" property with the schemas for all derived types.
 092                string typeDiscriminatorKey = polyOptions.TypeDiscriminatorPropertyName;
 093                List<JsonDerivedType> derivedTypes = new(polyOptions.DerivedTypes);
 94
 095                if (!typeInfo.Type.IsAbstract && !IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(typeInfo))
 096                {
 97                    // For non-abstract base types that haven't been explicitly configured,
 98                    // add a trivial schema to the derived types since we should support it.
 099                    derivedTypes.Add(new JsonDerivedType(typeInfo.Type));
 0100                }
 101
 0102                bool containsTypesWithoutDiscriminator = derivedTypes.Exists(static derivedTypes => derivedTypes.TypeDis
 0103                JsonSchemaType schemaType = JsonSchemaType.Any;
 0104                List<JsonSchema>? anyOf = new(derivedTypes.Count);
 105
 0106                state.PushSchemaNode(JsonSchema.AnyOfPropertyName);
 107
 0108                foreach (JsonDerivedType derivedType in derivedTypes)
 0109                {
 0110                    Debug.Assert(derivedType.TypeDiscriminator is null or int or string);
 111
 0112                    KeyValuePair<string, JsonSchema>? derivedTypeDiscriminator = null;
 0113                    if (derivedType.TypeDiscriminator is { } discriminatorValue)
 0114                    {
 0115                        JsonNode discriminatorNode = discriminatorValue switch
 0116                        {
 0117                            string stringId => (JsonNode)stringId,
 0118                            _ => (JsonNode)(int)discriminatorValue,
 0119                        };
 120
 0121                        JsonSchema discriminatorSchema = new() { Constant = discriminatorNode };
 0122                        derivedTypeDiscriminator = new(typeDiscriminatorKey, discriminatorSchema);
 0123                    }
 124
 0125                    JsonTypeInfo derivedTypeInfo = typeInfo.Options.GetTypeInfoInternal(derivedType.DerivedType);
 126
 0127                    state.PushSchemaNode(anyOf.Count.ToString(CultureInfo.InvariantCulture));
 0128                    JsonSchema derivedSchema = MapJsonSchemaCore(
 0129                        ref state,
 0130                        derivedTypeInfo,
 0131                        parentPolymorphicTypeInfo: typeInfo,
 0132                        typeDiscriminator: derivedTypeDiscriminator,
 0133                        parentPolymorphicTypeContainsTypesWithoutDiscriminator: containsTypesWithoutDiscriminator,
 0134                        parentPolymorphicTypeIsNonNullable: propertyInfo is { IsGetNullable: false, IsSetNullable: false
 0135                        cacheResult: false);
 136
 0137                    state.PopSchemaNode();
 138
 139                    // Determine if all derived schemas have the same type.
 0140                    if (anyOf.Count == 0)
 0141                    {
 0142                        schemaType = derivedSchema.Type;
 0143                    }
 0144                    else if (schemaType != derivedSchema.Type)
 0145                    {
 0146                        schemaType = JsonSchemaType.Any;
 0147                    }
 148
 0149                    anyOf.Add(derivedSchema);
 0150                }
 151
 0152                state.PopSchemaNode();
 153
 0154                if (schemaType is not JsonSchemaType.Any)
 0155                {
 156                    // If all derived types have the same schema type, we can simplify the schema
 157                    // by moving the type keyword to the base schema and removing it from the derived schemas.
 0158                    foreach (JsonSchema derivedSchema in anyOf)
 0159                    {
 0160                        derivedSchema.Type = JsonSchemaType.Any;
 161
 0162                        if (derivedSchema.KeywordCount == 0)
 0163                        {
 164                            // if removing the type results in an empty schema,
 165                            // remove the anyOf array entirely since it's always true.
 0166                            anyOf = null;
 0167                            break;
 168                        }
 0169                    }
 0170                }
 171
 0172                return CompleteSchema(ref state, new()
 0173                {
 0174                    Type = schemaType,
 0175                    AnyOf = anyOf,
 0176                    // If all derived types have a discriminator, we can require it in the base schema.
 0177                    Required = containsTypesWithoutDiscriminator ? null : [typeDiscriminatorKey]
 0178                });
 179            }
 180
 0181            if (effectiveConverter.NullableElementConverter is { } elementConverter)
 0182            {
 0183                JsonTypeInfo elementTypeInfo = typeInfo.Options.GetTypeInfo(elementConverter.Type!);
 0184                schema = MapJsonSchemaCore(ref state, elementTypeInfo, customConverter: elementConverter, customNumberHa
 185
 0186                if (elementConverter.IsIeeeFloatingPointConverter &&
 0187                    (effectiveNumberHandling & JsonNumberHandling.AllowNamedFloatingPointLiterals) != 0)
 0188                {
 189                    // IEEE floating-point types with AllowNamedFloatingPointLiterals generate an anyOf schema.
 190                    // Fold null into the numeric branch to preserve nullability for nullable wrappers.
 0191                    Debug.Assert(schema.AnyOf is not null, "IEEE floating-point types with AllowNamedFloatingPointLitera
 192
 0193                    List<JsonSchema> anyOf = schema.AnyOf;
 0194                    Debug.Assert(anyOf.Exists(b => (b.Type & JsonSchemaType.Number) != 0),
 0195                        "IEEE floating-point anyOf schema should have a numeric branch.");
 196
 0197                    foreach (JsonSchema branch in anyOf)
 0198                    {
 0199                        if ((branch.Type & JsonSchemaType.Number) != 0)
 0200                        {
 0201                            branch.Type |= JsonSchemaType.Null;
 0202                            break;
 203                        }
 0204                    }
 0205                }
 0206                else if (schema.Enum is not null)
 0207                {
 0208                    Debug.Assert(elementTypeInfo.Type.IsEnum, "The enum keyword should only be populated by schemas for 
 0209                    schema.Enum.Add(null); // Append null to the enum array.
 0210                }
 211
 0212                return CompleteSchema(ref state, schema);
 213            }
 214
 0215            switch (typeInfo.Kind)
 216            {
 217                case JsonTypeInfoKind.Object:
 0218                    List<KeyValuePair<string, JsonSchema>>? properties = null;
 0219                    List<string>? required = null;
 0220                    JsonSchema? additionalProperties = null;
 221
 0222                    if (typeInfo.EffectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
 0223                    {
 0224                        additionalProperties = JsonSchema.CreateFalseSchema();
 0225                    }
 226
 0227                    if (typeDiscriminator is { } typeDiscriminatorPair)
 0228                    {
 0229                        (properties ??= []).Add(typeDiscriminatorPair);
 0230                        if (parentPolymorphicTypeContainsTypesWithoutDiscriminator)
 0231                        {
 232                            // Require the discriminator here since it's not common to all derived types.
 0233                            (required ??= []).Add(typeDiscriminatorPair.Key);
 0234                        }
 0235                    }
 236
 0237                    state.PushSchemaNode(JsonSchema.PropertiesPropertyName);
 0238                    foreach (JsonPropertyInfo property in typeInfo.Properties)
 0239                    {
 0240                        if (property is { Get: null, Set: null } or { IsExtensionData: true })
 0241                        {
 0242                            continue; // Skip JsonIgnored properties and extension data
 243                        }
 244
 0245                        state.PushSchemaNode(property.Name);
 0246                        JsonSchema propertySchema = MapJsonSchemaCore(
 0247                            ref state,
 0248                            property.JsonTypeInfo,
 0249                            propertyInfo: property,
 0250                            customConverter: property.EffectiveConverter,
 0251                            customNumberHandling: property.EffectiveNumberHandling);
 252
 0253                        state.PopSchemaNode();
 254
 0255                        if (property.AssociatedParameter is { HasDefaultValue: true } parameterInfo)
 0256                        {
 0257                            JsonSchema.EnsureMutable(ref propertySchema);
 0258                            propertySchema.DefaultValue = JsonSerializer.SerializeToNode(parameterInfo.EffectiveDefaultV
 0259                            propertySchema.HasDefaultValue = true;
 0260                        }
 261
 0262                        (properties ??= []).Add(new(property.Name, propertySchema));
 263
 264                        // Mark as required if either the property is required or the associated constructor parameter i
 265                        // While the latter implies the former in cases where the JsonSerializerOptions.RespectRequiredC
 266                        // setting has been enabled, for the case of the schema exporter we always mark non-optional con
 0267                        if (property is { IsRequired: true } or { AssociatedParameter.IsRequiredParameter: true })
 0268                        {
 0269                            (required ??= []).Add(property.Name);
 0270                        }
 0271                    }
 272
 0273                    state.PopSchemaNode();
 0274                    return CompleteSchema(ref state, new()
 0275                    {
 0276                        Type = JsonSchemaType.Object,
 0277                        Properties = properties,
 0278                        Required = required,
 0279                        AdditionalProperties = additionalProperties,
 0280                    });
 281
 282                case JsonTypeInfoKind.Enumerable:
 0283                    Debug.Assert(typeInfo.ElementTypeInfo is not null);
 284
 0285                    if (typeDiscriminator is null)
 0286                    {
 0287                        state.PushSchemaNode(JsonSchema.ItemsPropertyName);
 0288                        JsonSchema items = MapJsonSchemaCore(ref state, typeInfo.ElementTypeInfo, customNumberHandling: 
 0289                        state.PopSchemaNode();
 290
 0291                        return CompleteSchema(ref state, new()
 0292                        {
 0293                            Type = JsonSchemaType.Array,
 0294                            Items = items.IsTrue ? null : items,
 0295                        });
 296                    }
 297                    else
 0298                    {
 299                        // Polymorphic enumerable types are represented using a wrapping object:
 300                        // { "$type" : "discriminator", "$values" : [element1, element2, ...] }
 301                        // Which corresponds to the schema
 302                        // { "properties" : { "$type" : { "const" : "discriminator" }, "$values" : { "type" : "array", "
 303                        const string ValuesKeyword = JsonSerializer.ValuesPropertyName;
 304
 0305                        state.PushSchemaNode(JsonSchema.PropertiesPropertyName);
 0306                        state.PushSchemaNode(ValuesKeyword);
 0307                        state.PushSchemaNode(JsonSchema.ItemsPropertyName);
 308
 0309                        JsonSchema items = MapJsonSchemaCore(ref state, typeInfo.ElementTypeInfo, customNumberHandling: 
 310
 0311                        state.PopSchemaNode();
 0312                        state.PopSchemaNode();
 0313                        state.PopSchemaNode();
 314
 0315                        return CompleteSchema(ref state, new()
 0316                        {
 0317                            Type = JsonSchemaType.Object,
 0318                            Properties =
 0319                            [
 0320                                typeDiscriminator.Value,
 0321                                new(ValuesKeyword,
 0322                                    new JsonSchema()
 0323                                    {
 0324                                        Type = JsonSchemaType.Array,
 0325                                        Items = items.IsTrue ? null : items,
 0326                                    }),
 0327                            ],
 0328                            Required = parentPolymorphicTypeContainsTypesWithoutDiscriminator ? [typeDiscriminator.Value
 0329                        });
 330                    }
 331
 332                case JsonTypeInfoKind.Dictionary:
 0333                    Debug.Assert(typeInfo.ElementTypeInfo is not null);
 334
 0335                    List<KeyValuePair<string, JsonSchema>>? dictProps = null;
 0336                    List<string>? dictRequired = null;
 337
 0338                    if (typeDiscriminator is { } dictDiscriminator)
 0339                    {
 0340                        dictProps = [dictDiscriminator];
 0341                        if (parentPolymorphicTypeContainsTypesWithoutDiscriminator)
 0342                        {
 343                            // Require the discriminator here since it's not common to all derived types.
 0344                            dictRequired = [dictDiscriminator.Key];
 0345                        }
 0346                    }
 347
 0348                    state.PushSchemaNode(JsonSchema.AdditionalPropertiesPropertyName);
 0349                    JsonSchema valueSchema = MapJsonSchemaCore(ref state, typeInfo.ElementTypeInfo, customNumberHandling
 0350                    state.PopSchemaNode();
 351
 0352                    return CompleteSchema(ref state, new()
 0353                    {
 0354                        Type = JsonSchemaType.Object,
 0355                        Properties = dictProps,
 0356                        Required = dictRequired,
 0357                        AdditionalProperties = valueSchema.IsTrue ? null : valueSchema,
 0358                    });
 359
 360                case JsonTypeInfoKind.Union:
 0361                    if (typeInfo.UnionCases is { Count: > 0 } unionCases)
 0362                    {
 0363                        JsonSchemaType unionSchemaType = JsonSchemaType.Any;
 0364                        List<JsonSchema>? unionAnyOf = new(unionCases.Count);
 365
 0366                        state.PushSchemaNode(JsonSchema.AnyOfPropertyName);
 367
 0368                        foreach (JsonUnionCaseInfo caseInfo in unionCases)
 0369                        {
 0370                            JsonTypeInfo caseTypeInfo = typeInfo.Options.GetTypeInfoInternal(caseInfo.CaseType);
 371
 0372                            state.PushSchemaNode(unionAnyOf.Count.ToString(CultureInfo.InvariantCulture));
 0373                            JsonSchema caseSchema = MapJsonSchemaCore(ref state, caseTypeInfo, customNumberHandling: typ
 0374                            state.PopSchemaNode();
 375
 0376                            if (caseInfo.IsNullable)
 0377                            {
 0378                                caseSchema.Type |= JsonSchemaType.Null;
 0379                            }
 380
 0381                            if (unionAnyOf.Count == 0)
 0382                            {
 0383                                unionSchemaType = caseSchema.Type;
 0384                            }
 0385                            else if (unionSchemaType != caseSchema.Type)
 0386                            {
 0387                                unionSchemaType = JsonSchemaType.Any;
 0388                            }
 389
 0390                            unionAnyOf.Add(caseSchema);
 0391                        }
 392
 0393                        state.PopSchemaNode();
 394
 0395                        if (unionSchemaType is not JsonSchemaType.Any)
 0396                        {
 0397                            foreach (JsonSchema caseSchema in unionAnyOf)
 0398                            {
 0399                                caseSchema.Type = JsonSchemaType.Any;
 400
 0401                                if (caseSchema.KeywordCount == 0)
 0402                                {
 0403                                    unionAnyOf = null;
 0404                                    break;
 405                                }
 0406                            }
 0407                        }
 408
 0409                        return CompleteSchema(ref state, new()
 0410                        {
 0411                            Type = unionSchemaType,
 0412                            AnyOf = unionAnyOf,
 0413                        });
 414                    }
 415
 0416                    return CompleteSchema(ref state, JsonSchema.CreateTrueSchema());
 417
 418                default:
 0419                    Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.None);
 420                    // Return a `true` schema for types with user-defined converters.
 0421                    return CompleteSchema(ref state, JsonSchema.CreateTrueSchema());
 422            }
 423
 424            JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema)
 0425            {
 0426                if (HasObsoleteAttribute(typeInfo.Type) ||
 0427                    HasObsoleteAttribute(propertyInfo?.AttributeProvider))
 0428                {
 0429                    JsonSchema.EnsureMutable(ref schema);
 0430                    schema.Deprecated = true;
 0431                }
 432
 0433                if (schema.Ref is null)
 0434                {
 0435                    if (IsNullableSchema(state.ExporterOptions))
 0436                    {
 0437                        schema.MakeNullable();
 0438                    }
 439
 440                    bool IsNullableSchema(JsonSchemaExporterOptions options)
 0441                    {
 442                        // A schema is marked as nullable if either:
 443                        // 1. We have a schema for a property where either the getter or setter are marked as nullable.
 444                        // 2. We have a schema for a Nullable<T> type.
 445                        // 3. We have a schema for a reference type, unless we're explicitly treating null-oblivious typ
 446
 0447                        if (propertyInfo is not null)
 0448                        {
 0449                            if (propertyInfo.Get is not null && propertyInfo.IsGetNullable)
 0450                            {
 0451                                return true;
 452                            }
 453
 0454                            if (propertyInfo.AssociatedParameter is not null)
 0455                            {
 0456                                return propertyInfo.AssociatedParameter.IsNullable;
 457                            }
 458
 0459                            return propertyInfo.Set is not null && propertyInfo.IsSetNullable;
 460                        }
 461
 0462                        if (typeInfo.IsNullable)
 0463                        {
 0464                            return true;
 465                        }
 466
 0467                        return !typeInfo.Type.IsValueType && !parentPolymorphicTypeIsNonNullable && !options.TreatNullOb
 0468                    }
 0469                }
 470
 0471                if (state.ExporterOptions.TransformSchemaNode is not null)
 0472                {
 473                    // Prime the schema for invocation by the JsonNode transformer.
 0474                    schema.ExporterContext = exporterContext;
 0475                }
 476
 0477                return schema;
 0478            }
 0479        }
 480
 481        private static void ValidateOptions(JsonSerializerOptions options)
 0482        {
 0483            if (options.ReferenceHandler == ReferenceHandler.Preserve)
 0484            {
 0485                ThrowHelper.ThrowNotSupportedException_JsonSchemaExporterDoesNotSupportReferenceHandlerPreserve();
 486            }
 487
 0488            options.MakeReadOnly();
 0489        }
 490
 491        private static bool HasObsoleteAttribute(ICustomAttributeProvider? attributeProvider)
 0492        {
 0493            if (attributeProvider is null)
 0494            {
 0495                return false;
 496            }
 497
 498            // Identify ObsoleteAttribute using its full type name rather than typeof(ObsoleteAttribute).
 499            // On downlevel targets System.Text.Json compiles in an internal ObsoleteAttribute polyfill
 500            // that would otherwise shadow the framework type, causing the typeof comparison to never match
 501            // the ObsoleteAttribute applied by user code.
 0502            foreach (object attribute in attributeProvider.GetCustomAttributes(inherit: true))
 0503            {
 0504                if (attribute.GetType().FullName == "System.ObsoleteAttribute")
 0505                {
 0506                    return true;
 507                }
 0508            }
 509
 0510            return false;
 0511        }
 512
 513        private static bool IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(JsonTypeInfo typeInfo)
 0514        {
 0515            Debug.Assert(typeInfo.PolymorphismOptions is not null);
 516
 0517            foreach (JsonDerivedType derivedType in typeInfo.PolymorphismOptions.DerivedTypes)
 0518            {
 0519                if (derivedType.DerivedType == typeInfo.Type)
 0520                {
 0521                    return true;
 522                }
 0523            }
 524
 0525            return false;
 0526        }
 527
 528        private readonly ref struct GenerationState(JsonSerializerOptions options, JsonSchemaExporterOptions exporterOpt
 529        {
 0530            private readonly List<string> _currentPath = [];
 0531            private readonly Dictionary<(JsonTypeInfo, JsonPropertyInfo?), string[]> _generated = new();
 532
 0533            public int CurrentDepth => _currentPath.Count;
 0534            public JsonSerializerOptions Options { get; } = options;
 0535            public JsonSchemaExporterOptions ExporterOptions { get; } = exporterOptions;
 536
 537            public void PushSchemaNode(string nodeId)
 0538            {
 0539                if (CurrentDepth == Options.EffectiveMaxDepth)
 0540                {
 0541                    ThrowHelper.ThrowInvalidOperationException_JsonSchemaExporterDepthTooLarge();
 0542                }
 543
 0544                _currentPath.Add(nodeId);
 0545            }
 546
 547            public void PopSchemaNode()
 0548            {
 0549                Debug.Assert(CurrentDepth > 0);
 0550                _currentPath.RemoveAt(_currentPath.Count - 1);
 0551            }
 552
 553            /// <summary>
 554            /// Registers the current schema node generation context; if it has already been generated return a JSON poi
 555            /// </summary>
 556            public bool TryGetExistingJsonPointer(in JsonSchemaExporterContext context, [NotNullWhen(true)] out string? 
 0557            {
 0558                (JsonTypeInfo TypeInfo, JsonPropertyInfo? PropertyInfo) key = (context.TypeInfo, context.PropertyInfo);
 559#if NET
 0560                ref string[]? pathToSchema = ref CollectionsMarshal.GetValueRefOrAddDefault(_generated, key, out bool ex
 561#else
 562                bool exists = _generated.TryGetValue(key, out string[]? pathToSchema);
 563#endif
 0564                if (exists)
 0565                {
 0566                    existingJsonPointer = FormatJsonPointer(pathToSchema);
 0567                    return true;
 568                }
 569#if NET
 0570                pathToSchema = context._path;
 571#else
 572                _generated[key] = context._path;
 573#endif
 0574                existingJsonPointer = null;
 0575                return false;
 0576            }
 577
 578            public JsonSchemaExporterContext CreateContext(JsonTypeInfo typeInfo, JsonPropertyInfo? propertyInfo, JsonTy
 0579            {
 0580                return new JsonSchemaExporterContext(typeInfo, propertyInfo, baseTypeInfo, [.. _currentPath]);
 0581            }
 582
 583            private static string FormatJsonPointer(ReadOnlySpan<string> path)
 0584            {
 0585                if (path.IsEmpty)
 0586                {
 0587                    return "#";
 588                }
 589
 0590                using ValueStringBuilder sb = new(initialCapacity: path.Length * 10);
 0591                sb.Append('#');
 592
 0593                foreach (string segment in path)
 0594                {
 0595                    sb.Append('/');
 596
 597                    // Per RFC 6901 the characters '~' and '/' are escaped as '~0' and '~1'.
 0598                    string escapedToken = segment.Replace("~", "~0").Replace("/", "~1");
 599
 600                    // Per RFC 6901 section 6 the JSON Pointer is embedded in a URI fragment,
 601                    // so percent-encode any characters that are not valid in a URI fragment.
 0602                    sb.Append(Uri.EscapeDataString(escapedToken));
 0603                }
 604
 0605                return sb.ToString();
 0606            }
 607        }
 608    }
 609}
 610

Methods/Properties

GetJsonSchemaAsNode(System.Text.Json.JsonSerializerOptions,System.Type,System.Text.Json.Schema.JsonSchemaExporterOptions)
GetJsonSchemaAsNode(System.Text.Json.Serialization.Metadata.JsonTypeInfo,System.Text.Json.Schema.JsonSchemaExporterOptions)
MapJsonSchemaCore(System.Text.Json.Schema.JsonSchemaExporter/GenerationState&,System.Text.Json.Serialization.Metadata.JsonTypeInfo,System.Text.Json.Serialization.Metadata.JsonPropertyInfo,System.Text.Json.Serialization.JsonConverter,System.Nullable`1<System.Text.Json.Serialization.JsonNumberHandling>,System.Text.Json.Serialization.Metadata.JsonTypeInfo,System.Boolean,System.Boolean,System.Nullable`1<System.Collections.Generic.KeyValuePair`2<System.String,System.Text.Json.Schema.JsonSchema>>,System.Boolean)
ValidateOptions(System.Text.Json.JsonSerializerOptions)
HasObsoleteAttribute(System.Reflection.ICustomAttributeProvider)
IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(System.Text.Json.Serialization.Metadata.JsonTypeInfo)
.ctor(System.Text.Json.JsonSerializerOptions,System.Text.Json.Schema.JsonSchemaExporterOptions)
CurrentDepth()
Options()
ExporterOptions()
PushSchemaNode(System.String)
PopSchemaNode()
TryGetExistingJsonPointer(System.Text.Json.Schema.JsonSchemaExporterContext&,System.String&)
CreateContext(System.Text.Json.Serialization.Metadata.JsonTypeInfo,System.Text.Json.Serialization.Metadata.JsonPropertyInfo,System.Text.Json.Serialization.Metadata.JsonTypeInfo)
FormatJsonPointer(System.ReadOnlySpan`1<System.String>)