Newtonsoft.Json.Schema is not validating JSON input correctly against a JSON schema
In below example schema validation is returning true for the below payload JSON, even though:
- for "mCRO" nullable is set to false
- iNumber" has varchar(10)
Why are these errors not getting caught?
We are using Newtonsoft.Json.Schema for validation.
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# Code:
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
};
}
Why are these errors not getting caught?
We are using Newtonsoft.Json.Schema for validation.
2
Answers
To echo and expand on Daniel’s comment, OpenAPI contains schemas, but it is not itself a schema. (Technically, the schemas that OpenAPI 3.0 and earlier use are a modified JSON Schema Draft 4. Version 3.1 uses JSON Schema DRaft 2020-12 directly.)
You’ll need an OpenAPI tool, like OpenAPI.Net.
To further expand,
format: varchar(10)
is not a format recognized by OpenAPI for validation. You can find their supported formats in the registry they maintain https://spec.openapis.org/registry/format/type: string
is matching your data.JSON Schema doesn’t use the
format
keyword for validation unless the implementation you are using has explicitly implemented it. By default, it’s not required to be supported by the JSON Schema specificationBe aware,
nullable
is NOT a JSON Schema keyword, and thus not recognized during validation. This keyword is only defined by OpenAPI and enforced by the OpenAPI 3.0.x metaschema.Ultimately, there are a lot of nuances with using OAS 3.0.x and a JSON Schema validator. It’s recommended to use the latest OAS 3.1.x version which aligns closer to the actual JSON Schema specification.