OpenAPI(Swagger)与JSON Schema空值校验兼容性问题的.NET解决方案咨询
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, orminimumbased on your needs.
内容的提问来源于stack exchange,提问作者Ral

