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

使用Newtonsoft.Json.Schema验证JSON时未捕获错误的原因排查

使用Newtonsoft.Json.Schema验证JSON时的未捕获错误问题

我们在使用Newtonsoft.Json.Schema进行JSON验证时遇到异常:尽管OpenAPI Schema中mCRO字段的nullable设为false、iNumber字段格式定义为varchar(10),但包含mCRO为null、iNumber超长的Payload JSON验证返回true,未捕获这些错误。


相关代码与Schema

JSON Schema

{
    "openapi": "3.0.2",
    "info": {
        "title": "Demo API",
        "version": "1.1.1",
        "description": "<b>Demo version"
    },
    "tags": [
        {
            "name": "Records",
            "description": "Registration"
        }
    ],
    "paths": {
        "/records/AddUpdateRecord": {
            "post": {
                "tags": ["Records"],
                "description": "Add or Update a Object",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {"$ref": "#/components/schemas/addUpdateRecords"}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK - Indicates that the request has succeeded.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse200"}
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse400"}
                            }
                        }
                    },
                    "401": {"description": "Unauthorized "},
                    "403": {"description": "Forbidden - Unauthorized request."},
                    "429": {"description": "Too Many Requests "},
                    "500": {"description": "Internal Server Error "}
                }
            }
        },
        "/records/AddUpdateC": {
            "post": {
                "tags": ["Records"],
                "description": "Add or Update existing 1 to N Coverages.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {"$ref": "#/components/schemas/addUpdateC"}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK - Indicates that the request has succeeded.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse200"}
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request ",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponseC400"}
                            }
                        }
                    },
                    "401": {"description": "Unauthorized ."},
                    "403": {"description": "Forbidden."},
                    "429": {"description": "Too Many Requests "},
                    "500": {"description": "Internal Server Error "}
                }
            }
        },
        "/records/UpdateRO": {
            "put": {
                "tags": ["Records"],
                "description": "Update an existing RO.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {"$ref": "#/components/schemas/updateRO"}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK - Indicates that the request has succeeded.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse200"}
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request ",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse400"}
                            }
                        }
                    },
                    "401": {"description": "Unauthorized"},
                    "403": {"description": "Forbidden ."},
                    "429": {"description": "Too Many Requests ."},
                    "500": {"description": "Internal Server Error "}
                }
            }
        },
        "/records/RemoveRO": {
            "post": {
                "tags": ["Records"],
                "description": "Remove an existing RO",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {"$ref": "#/components/schemas/removeRO"}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK - Indicates that the request has succeeded.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse200"}
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse400"}
                            }
                        }
                    },
                    "401": {"description": "Unauthorized."},
                    "403": {"description": "Forbidden"},
                    "429": {"description": "Too Many Requests ."},
                    "500": {"description": "Internal Server Error"}
                }
            }
        },
        "/records/MoveP": {
            "post": {
                "tags": ["Records"],
                "description": "Move",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {"$ref": "#/components/schemas/moveP"}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK - Indicates that the request has succeeded.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse200"}
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/httpResponse400"}
                            }
                        }
                    },
                    "401": {"description": "Unauthorized ."},
                    "403": {"description": "Forbidden "},
                    "429": {"description": "Too Many Requests ."},
                    "500": {"description": "Internal Server Error."}
                }
            }
        }
    },
    "components": {
        "schemas": {
            "httpResponse200": {
                "type": "object",
                "properties": {
                    "results": {"nullable": true, "example": null},
                    "requestGuid": {"type": "string", "example": "GUI"},
                    "errors": {"nullable": true, "example": null},
                    "httpStatusCode": {"type": "integer", "nullable": false, "example": 200}
                }
            },
            "addUpdateC": {
                "type": "object",
                "properties": {
                    "iCR": {
                        "type": "array",
                        "description": "1 to N",
                        "items": {
                            "type": "object",
                            "properties": {
                                "requestGuid": {"type": "string", "example": "GUI", "nullable": false},
                                "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false},
                                "eTypeRequest": {
                                    "type": "array",
                                    "description": "1 to N",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "requestGuid": {"type": "string", "example": "16-byte binary value", "nullable": false},
                                            "eTypeECode": {"type": "string", "format": "varchar(3)", "nullable": false, "description": "List"}
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "moveP": {
                "type": "object",
                "properties": {
                    "iMPR": {
                        "type": "object",
                        "properties": {
                            "requestGuid": {"type": "string", "example": "GUID", "nullable": false},
                            "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false}
                        }
                    }
                }
            },
            "removeRO": {
                "type": "object",
                "properties": {
                    "iRROR": {
                        "type": "object",
                        "properties": {
                            "requestGuid": {"type": "string", "example": "GUID", "nullable": false},
                            "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false},
                            "mCRO": {"type": "number", "format": "smallint", "nullable": false, "description": "List"}
                        }
                    }
                }
            },
            "addUpdateRecords": {
                "type": "object",
                "properties": {
                    "iROR": {
                        "type": "object",
                        "properties": {
                            "requestGuid": {"type": "string", "example": "GUID", "nullable": false},
                            "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false},
                            "mCRO": {"type": "number", "format": "smallint", "nullable": false, "description": "List"}
                        }
                    },
                    "iCR": {
                        "type": "array",
                        "description": "1 to N",
                        "items": {
                            "type": "object",
                            "properties": {
                                "requestGuid": {"type": "string", "example": "16-byte binary value", "nullable": false},
                                "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false},
                                "eTypeRequest": {
                                    "type": "array",
                                    "description": "1 to N",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "requestGuid": {"type": "string", "example": "16-byte binary value", "nullable": false},
                                            "eTypeECode": {"type": "string", "format": "varchar(3)", "nullable": false, "description": "List"}
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "updateRO": {
                "type": "object",
                "properties": {
                    "iROR": {
                        "type": "object",
                        "properties": {
                            "requestGuid": {"type": "string", "example": "GUID", "nullable": false},
                            "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false},
                            "mCRO": {"type": "number", "format": "smallint", "nullable": false, "description": "List"}
                        }
                    }
                }
            },
            "httpResponse400": {
                "type": "object",
                "properties": {
                    "results": {"nullable": true, "example": null},
                    "requestGuid": {"type": "string", "example": "GUI"},
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "cTypeCode": {"nullable": true, "example": null},
                                "cFormula": {"nullable": true, "example": null},
                                "errorDescription": {"type": "string"}
                            }
                        }
                    },
                    "httpStatusCode": {"type": "integer", "nullable": false, "example": 400}
                }
            },
            "httpResponseC400": {
                "type": "object",
                "properties": {
                    "results": {"nullable": true, "example": null},
                    "requestGuid": {"type": "string", "example": "GUI "},
                    "errors": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "cTypeCode": {"nullable": true, "example": null},
                                "cFormula": {"nullable": true, "example": null},
                                "errorDescription": {"type": "string"}
                            }
                        }
                    },
                    "httpStatusCode": {"type": "integer", "nullable": false, "example": 400}
                }
            }
        }
    }
}

Payload JSON

{
  "iRROR": {
    "requestGuid": "GUID",
    "iNumber": "12345678910111213",
    "mCRO": null
  }
}

C# 验证代码

using (StreamReader file = File.OpenText(@"C:\Sample\OpenAPISpecification.json"))
using (JsonTextReader reader = new JsonTextReader(file))
{
    JSchema schema = JSchema.Load(reader);
    JObject json = JObject.Parse(File.ReadAllText(@"C:\Sample\iRROR.json"));
    
    //validate json
    IList<ValidationError> errors;
    bool valid = json.IsValid(schema, out errors);
    
    new ValidateResponse
    {
        Valid = valid,
        Errors = errors
    };
}

问题原因与解决方案

核心问题分析

  1. Schema加载错误:代码直接加载了整个OpenAPI文档作为验证Schema,但OpenAPI根结构不是请求体的Schema,应该提取components/schemas/removeRO节点作为验证目标。
  2. nullable关键字不兼容:Newtonsoft.Json.Schema默认不识别OpenAPI的nullable扩展字段,这是OpenAPI特有的定义,而非标准JSON Schema规范。
  3. 自定义format无默认验证:varchar(10)是自定义格式,不属于标准JSON Schema的内置格式(如email、date-time),Newtonsoft.Json.Schema不会自动验证其长度限制。

解决方案

1. 加载正确的目标Schema

修改代码,提取OpenAPI文档中对应的请求体Schema:

using (StreamReader file = File.OpenText(@"C:\Sample\OpenAPISpecification.json"))
using (JsonTextReader reader = new JsonTextReader(file))
{
    // 先加载整个OpenAPI文档
    JObject openApiDoc = JObject.Load(reader);
    // 提取removeRO的Schema节点
    JToken targetSchemaToken = openApiDoc.SelectToken("$.components.schemas.removeRO");
    JSchema schema = JSchema.Parse(targetSchemaToken.ToString());
    
    JObject json = JObject.Parse(File.ReadAllText(@"C:\Sample\iRROR.json"));
    
    IList<ValidationError> errors;
    bool valid = json.IsValid(schema, out errors);
    
    var response = new ValidateResponse
    {
        Valid = valid,
        Errors = errors
    };
}

2. 处理nullable验证

将OpenAPI的nullable: false转换为标准JSON Schema的required配置,确保字段必填且类型非空:
修改removeRO的Schema,添加required数组:

"removeRO": {
    "type": "object",
    "properties": {
        "iRROR": {
            "type": "object",
            "properties": {
                "requestGuid": {"type": "string", "example": "GUID", "nullable": false},
                "iNumber": {"type": "string", "format": "varchar(10)", "nullable": false},
                "mCRO": {"type": "number", "format": "smallint", "nullable": false, "description": "List"}
            },
            // 指定必填字段
            "required": ["requestGuid", "iNumber", "mCRO"]
        }
    },
    "required": ["iRROR"]
}

3. 实现自定义varchar长度验证

创建自定义验证器处理varchar(N)格式的长度限制:

public class VarcharLengthValidator : IValidator
{
    public bool CanValidate(JSchema schema)
    {
        return schema.Type == JSchemaType.String 
               && !string.IsNullOrEmpty(schema.Format) 
               && schema.Format.StartsWith("varchar(");
    }

    public void Validate(JToken value, JSchema schema, ValidationContext context, IList<ValidationError> errors)
    {
        if (value.Type != JTokenType.String) return;
        
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:09:53