You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenAPI(Swagger)与JSON Schema空值校验兼容性问题的.NET解决方案咨询

Solution for OpenAPI 3.0 nullable:true Compatibility with Json.NET Schema Validation

I’ve dealt with this exact conflict between OpenAPI 3.0’s null handling and Json.NET Schema’s validation rules before. The root problem is straightforward: OpenAPI 3.0 uses the nullable:true keyword to denote nullable fields, but Json.NET Schema follows the JSON Schema specification, which requires a type array (like ["string", "null"]) to allow null values.

The good news is you can keep your valid OpenAPI 3.0 schema as-is, and transform it in memory to work with Json.NET Schema. Below is a step-by-step C# implementation that handles all nullable types—including complex nested objects, arrays, and composite schemas like allOf/anyOf.

Step 1: Install Required NuGet Packages

First, add the packages needed to parse OpenAPI documents and work with Json.NET Schema:

Install-Package Microsoft.OpenApi
Install-Package Newtonsoft.Json.Schema

Step 2: Load and Process the OpenAPI Schema

We’ll first read the OpenAPI document, then recursively update all schemas with nullable:true to use the JSON Schema-compatible type array format.

using Microsoft.OpenApi;
using Microsoft.OpenApi.Readers;
using Newtonsoft.Json.Schema;
using System.IO;
using System.Linq;

// Load your OpenAPI/Swagger file
var openApiStream = File.OpenRead("your-swagger-document.json");
var reader = new OpenApiStreamReader();
var openApiDoc = reader.Read(openApiStream, out var diagnostics);

// Check for document parsing errors
if (diagnostics.Errors.Any())
{
    throw new InvalidOperationException(
        $"Failed to load OpenAPI document: {string.Join(", ", diagnostics.Errors.Select(e => e.Message))}");
}

// Recursively process all schemas to convert nullable:true to type arrays
void ProcessNullableSchemas(OpenApiSchema schema)
{
    if (schema == null) return;

    // Convert nullable:true to JSON Schema-compatible type array
    if (schema.Nullable)
    {
        if (schema.Type.Count == 1)
        {
            var originalType = schema.Type[0];
            schema.Type.Clear();
            schema.Type.Add(originalType);
            schema.Type.Add("null");
        }
        else if (!schema.Type.Contains("null"))
        {
            schema.Type.Add("null");
        }
        schema.Nullable = false; // No longer needed since we're using the type array
    }

    // Process nested properties
    if (schema.Properties != null)
    {
        foreach (var prop in schema.Properties.Values)
        {
            ProcessNullableSchemas(prop);
        }
    }

    // Process array items
    if (schema.Items != null)
    {
        ProcessNullableSchemas(schema.Items);
    }

    // Process composite schemas (allOf/anyOf/oneOf)
    foreach (var subSchema in schema.AllOf ?? Enumerable.Empty<OpenApiSchema>())
        ProcessNullableSchemas(subSchema);
    foreach (var subSchema in schema.AnyOf ?? Enumerable.Empty<OpenApiSchema>())
        ProcessNullableSchemas(subSchema);
    foreach (var subSchema in schema.OneOf ?? Enumerable.Empty<OpenApiSchema>())
        ProcessNullableSchemas(subSchema);

    // Process additionalProperties schema
    if (schema.AdditionalProperties is OpenApiSchema additionalSchema)
    {
        ProcessNullableSchemas(additionalSchema);
    }
}

// Apply the transformation to all schemas in the OpenAPI document's components
foreach (var schema in openApiDoc.Components.Schemas.Values)
{
    ProcessNullableSchemas(schema);
}

Step 3: Convert Processed OpenAPI Schema to Json.NET Schema

Next, we’ll map the processed OpenAPI schema to a JsonSchema object that Json.NET Schema can use for validation. This method covers common schema properties—you can extend it to include others like enum, minimum, or pattern as needed.

JsonSchema ConvertToJsonSchema(OpenApiSchema openApiSchema)
{
    if (openApiSchema == null) return null;

    var jsonSchema = new JsonSchema();

    // Map OpenAPI types to Json.NET Schema type flags
    foreach (var type in openApiSchema.Type)
    {
        jsonSchema.Type |= type switch
        {
            "string" => JsonSchema.Type.String,
            "number" => JsonSchema.Type.Number,
            "integer" => JsonSchema.Type.Integer,
            "boolean" => JsonSchema.Type.Boolean,
            "object" => JsonSchema.Type.Object,
            "array" => JsonSchema.Type.Array,
            "null" => JsonSchema.Type.Null,
            _ => JsonSchema.Type.None
        };
    }

    // Map format (e.g., "email", "date-time")
    if (!string.IsNullOrEmpty(openApiSchema.Format))
        jsonSchema.Format = openApiSchema.Format;

    // Map properties and their schemas
    if (openApiSchema.Properties != null)
    {
        jsonSchema.Properties = new Dictionary<string, JsonSchema>();
        foreach (var kvp in openApiSchema.Properties)
        {
            jsonSchema.Properties[kvp.Key] = ConvertToJsonSchema(kvp.Value);
        }
    }

    // Map required fields
    if (openApiSchema.Required != null)
        jsonSchema.Required = new HashSet<string>(openApiSchema.Required);

    // Map array items
    if (openApiSchema.Items != null)
        jsonSchema.Items.Add(ConvertToJsonSchema(openApiSchema.Items));

    // Map composite schemas
    if (openApiSchema.AllOf != null)
        jsonSchema.AllOf.AddRange(openApiSchema.AllOf.Select(ConvertToJsonSchema));
    if (openApiSchema.AnyOf != null)
        jsonSchema.AnyOf.AddRange(openApiSchema.AnyOf.Select(ConvertToJsonSchema));
    if (openApiSchema.OneOf != null)
        jsonSchema.OneOf.AddRange(openApiSchema.OneOf.Select(ConvertToJsonSchema));

    // Map additionalProperties rules
    if (openApiSchema.AdditionalProperties is OpenApiSchema additionalSchema)
        jsonSchema.AdditionalProperties = ConvertToJsonSchema(additionalSchema);
    else if (openApiSchema.AdditionalProperties is bool allowAdditional)
        jsonSchema.AdditionalProperties = allowAdditional;

    // Add other schema constraints as needed (example: min/max length)
    if (openApiSchema.MinLength.HasValue)
        jsonSchema.MinLength = openApiSchema.MinLength.Value;
    if (openApiSchema.MaxLength.HasValue)
        jsonSchema.MaxLength = openApiSchema.MaxLength.Value;

    return jsonSchema;
}

// Get the target schema you want to validate against (e.g., CalendarFunctionsDto)
var targetOpenApiSchema = openApiDoc.Components.Schemas["CalendarFunctionsDto"];
var jsonNetValidationSchema = ConvertToJsonSchema(targetOpenApiSchema);

Step 4: Validate JSON Data with Json.NET Schema

Finally, use the converted schema to validate your JSON payloads:

using Newtonsoft.Json.Linq;

// Example JSON payload with a null value
var testJson = JObject.Parse(@"{""description_EN"": null}");

// Run validation
var validationResults = jsonNetValidationSchema.Validate(testJson);

// Check results
if (validationResults.Any())
{
    foreach (var error in validationResults)
    {
        Console.WriteLine($"Validation Error: {error.Message}");
    }
}
else
{
    Console.WriteLine("Validation passed!");
}

Key Notes

  • Preserves OpenAPI Legality: We only modify the schema in memory—your original OpenAPI file stays valid and compliant with OpenAPI 3.0 specs.
  • Handles Complex Types: The recursive processing covers nested objects, arrays, and composite schemas (allOf, anyOf, oneOf), so it works for even the most complex data models.
  • Extensible: The conversion method can be extended to support additional OpenAPI schema properties like enum, default, or minimum based on your needs.

内容的提问来源于stack exchange,提问作者Ral

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 11:27:46