From c734b7a6c000d4566395272cbfc08d7c67fe91b4 Mon Sep 17 00:00:00 2001 From: Arpit Kuriyal Date: Mon, 18 Aug 2025 21:43:20 +0530 Subject: [PATCH 1/6] update readme file --- README.md | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 234 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d819e39..99257f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,32 @@ -# Hyperjump - Better JSON Schema Errors +# Hyperjump - Better JSON Schema Errors +It transforms standard, machine-oriented validation output into clear, human-friendly error messages ideal for API responses and developer tools. Built upon the official JSON Schema Output Format introduced in draft 2019-09, it ensures seamless compatibility with any compliant validator. -TODO: Write a short description of the package +### Node.js + +```bash +npm install @hyperjump/better-json-schema-errors +``` + +## API Error Message Format + +Our API Error Format includes :- +- **`schemaLocation`** - A JSON Pointer or URI that points to the specific keyword(s) within the schema that failed validation. This can be a single string or an array of absolute keyword locations for errors that are grouped together. + +- **`instanceLocation`** - JSON Pointer to the invalid piece of input data. + +- **`message`** - Human-friendly explanation of the validation error. + +Example:- +```json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/properties/name/minLength", + "instanceLocation": "#/name", + "message": "Expected a string at least 5 characters long." + } +} +``` ## Install @@ -8,16 +34,217 @@ This module is designed for node.js (ES Modules, TypeScript) and browsers. It should work in Bun and Deno as well, but the test runner doesn't work in these environments, so this module may be less stable in those environments. -### Node.js -```bash -npm install @hyperjump/better-json-schema-errors + +## Examples and Basic Usage +Better JSON Schema Errors works with **any JSON Schema validator** that follows the official [JSON Schema Output Format](https://json-schema.org/draft/2019-09). +In this example, we’ll showcase it with the [Hyperjump JSON Schema Validator](https://github.com/hyperjump-io/json-schema). + +Now let's define a schema and some invalid data, then run the validation and process the output with `better-json-schema-errors`. :- +```js +import { registerSchema, validate, unregisterSchema } from "@hyperjump/json-schema/draft-2020-12"; +import { betterJsonSchemaErrors } from "@hyperjump/better-json-schema-errors"; + +async function runExample() { + const schemaUri = "https://example.com/main"; + registerSchema({ + $schema: "https://json-schema.org/draft/2020-12/schema", + anyOf: [ + { enum: ["a", "b", "c"] } + ] + }, schemaUri); + + const instance = 4; + const result = await validate(schemaUri, instance, "BASIC"); + + if (result.valid) { + console.log("Valid instance!"); + } else { + const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance); + console.log(JSON.stringify(friendlyErrors, null, 2)); + } + + unregisterSchema(schemaUri); +} + +await runExample(); +``` +Output:- +```json +{ + "errors": [ + { + "message": "Unexpected value 4. Expected one of: 'a', 'b', or 'c'.", + "instanceLocation": "#", + "schemaLocation": "https://example.com/main#/enum" + } + ] +} +``` + +## Features and Advanced Usage +Better JSON Schema Errors goes beyond simply rephrasing validation messages. It provides a **robust error normalization layer** on top of the official [JSON Schema Output Format](https://json-schema.org/draft/2019-09), ensuring consistent, human-friendly results across different validators and error formats. +### 1. Works with All Output Formats +Supports all three standardized output formats: +- **BASIC** — The "Basic" structure is a flat list of output units +- **DETAILED** — The "Detailed" structure is based on the schema and can be more readable for both +humans and machines. +- **VERBOSE** — The "Verbose" structure is a fully realised hierarchy that exactly matches that of the +schema. + +No matter which output format your validator produces, Better JSON Schema Errors can process it. + +### 2. Resolves Output Ambiguities +Some validators may provide inconsistent or ambiguous data, such as using `keywordLocation` instead of the required `absoluteKeywordLocation`. Our library resolves these issues by normalizing both the `keywordLocation` and `instanceLocation`. It adds the leading `#` to `instanceLocation` and standardizes the path, so everything is a valid JSON Pointer and can be used predictably. This helps resolve ambiguities and provides a unified, reliable format for all error messages. + +### 3. Multiple Schema Locations +Sometimes a single validation issue is tied to **more than one schema keyword**. +For example, when both `minimum` and `exclusiveMinimum` apply, or when `minLength` and `maxLength` constraints overlap or when when both `minimum` and `maximum` apply. + +Instead of generating duplicate error messages, It groups these into an **array of schema locations** and produces one concise, human-friendly message :- + +```json +{ + "schemaLocation": [ + "https://example.com/main#/minimum", + "https://example.com/main#/maximum" + ], + "instanceLocation": "#/age", + "message": "Expected a number greater than 5 and less than 10." +} +``` +### 4. Localization + +The library uses a [fluent/bundle](https://github.com/projectfluent/fluent.js/tree/main/fluent-bundle) to provide localized error messages. By default, only English is supported. + +We need contributions from different countries to add more languages. + +To change the language, pass a language option to the betterJsonSchemaErrors function, like this: + ``` + const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance, "en-US"); +``` +### 5. Handling `anyOf` with Clarity + +The `anyOf` keyword is a powerful but complex JSON Schema feature. When an instance fails to validate against all subschemas within an `anyOf`, standard validator output can be verbose and confusing, often returning an error for each failed subschema. + +**Better JSON Schema Errors** intelligently simplifies this output, providing clear, consolidated error messages that are easier to debug. + +--- -## Examples and Usage +#### 1. Mismatched Types + +If the instance's type doesn't match any of the alternatives in an `anyOf`, the library provides a concise error message listing the expected types. + +**Schema:** +```json +{ + "anyOf": [ + { "type": "string" }, + { "type": "number" } + ] +} +``` + +Invalid Instance:- +``` Json +false +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf", + "instanceLocation": "#", + "message": "The instance should be of type 'string' or 'number' but found 'boolean'." + } + ] +} + +``` +#### 2. Partial Match with a Failed Constraint -TODO: Write some examples +If the instance's type matches one of the `anyOf` alternatives but fails a subsequent constraint (like `minLength`), our library correctly identifies the specific rule that was violated. +**Schema:** +```json +{ + "anyOf": [ + { "type": "string", "minLength": 5 }, + { "type": "number" } + ] +} +``` + +Invalid Instance:- +``` Json +"abc" +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf/0/minLength", + "instanceLocation": "#", + "message": "Expected a string at least 5 characters long." + } + ] +} + +``` + +#### 3. Multiple types match, pick based on field overlap + +When an instance matches multiple `anyOf` alternatives type, the library prioritizes the one with the most relevant error based on the instance's fields. + +**Schema:** +```json +{ + anyOf: [ + { + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" } + }, + required: ["name", "age"] + }, + { + type: "object", + properties: { + title: { type: "string" }, + author: { type: "string" }, + ID: { type: "string", pattern: "^[0-9\\-]+$" } + }, + required: ["title", "author", "ID"] + } + ] +} +``` + +Invalid Instance:- +``` Json +{ + title: "Clean Code", + author: "Robert Martin", + ID: "NotValidId" +}; +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf/1/properties/ID/pattern", + "instanceLocation": "#/ID", + "message": "The instance should match the format: "^[0-9\\-]+$". " + } + ] +} + +``` ## API @@ -29,7 +256,6 @@ changes you'd like to make before implementing it. If it's an obvious bug with an obvious solution or something simple like a fixing a typo, creating an issue isn't required. You can just send a PR without creating an issue. Before submitting any code, please remember to first run the following tests. - - `npm test` (Tests can also be run continuously using `npm test -- --watch`) - `npm run lint` - `npm run type-check` From 2792ad9b6f9cf8fdcf5abc17d63d970ea4e02c43 Mon Sep 17 00:00:00 2001 From: Arpit Kuriyal Date: Mon, 18 Aug 2025 21:56:02 +0530 Subject: [PATCH 2/6] correct minor mistake on the readme file --- README.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 99257f1..92ac34f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Example:- "instanceLocation": "#/name", "message": "Expected a string at least 5 characters long." } + ] } ``` @@ -202,23 +203,23 @@ When an instance matches multiple `anyOf` alternatives type, the library priorit **Schema:** ```json { - anyOf: [ + "anyOf": [ { - type: "object", - properties: { - name: { type: "string" }, - age: { type: "number" } + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "number" } }, - required: ["name", "age"] + "required": ["name", "age"] }, { - type: "object", - properties: { - title: { type: "string" }, - author: { type: "string" }, - ID: { type: "string", pattern: "^[0-9\\-]+$" } + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { "type": "string" }, + "ID": { "type": "string", "pattern": "^[0-9\\-]+$" } }, - required: ["title", "author", "ID"] + "required": ["title", "author", "ID"] } ] } @@ -227,10 +228,10 @@ When an instance matches multiple `anyOf` alternatives type, the library priorit Invalid Instance:- ``` Json { - title: "Clean Code", - author: "Robert Martin", - ID: "NotValidId" -}; + "title": "Clean Code", + "author": "Robert Martin", + "ID": "NotValidId" +} ``` BetterJSONSchemaErrors Output:- ``` Json @@ -239,7 +240,7 @@ BetterJSONSchemaErrors Output:- { "schemaLocation": "https://example.com/main#/anyOf/1/properties/ID/pattern", "instanceLocation": "#/ID", - "message": "The instance should match the format: "^[0-9\\-]+$". " + "message": "The instance should match the format: \"^[0-9\\-]+$\". " } ] } From f0154de1ebdb37c31e6b12aa722b9185c210dde9 Mon Sep 17 00:00:00 2001 From: Arpit Kuriyal Date: Tue, 19 Aug 2025 00:18:15 +0530 Subject: [PATCH 3/6] update the readme file and added docs folder --- .gitignore | 2 +- README.md | 176 ++++++++------------------------------------------ docs/anyOf.md | 119 ++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 150 deletions(-) create mode 100644 docs/anyOf.md diff --git a/.gitignore b/.gitignore index 73d8a43..833b127 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules/ -docs/ + scratch/ TODO* diff --git a/README.md b/README.md index 92ac34f..a51021c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ npm install @hyperjump/better-json-schema-errors ## API Error Message Format Our API Error Format includes :- -- **`schemaLocation`** - A JSON Pointer or URI that points to the specific keyword(s) within the schema that failed validation. This can be a single string or an array of absolute keyword locations for errors that are grouped together. +- **`schemaLocation`** - A JSON URI that points to the specific keyword(s) within the schema that failed validation. This can be a single string or an array of absolute keyword locations for errors that are grouped together. - **`instanceLocation`** - JSON Pointer to the invalid piece of input data. @@ -38,7 +38,7 @@ environments, so this module may be less stable in those environments. ## Examples and Basic Usage -Better JSON Schema Errors works with **any JSON Schema validator** that follows the official [JSON Schema Output Format](https://json-schema.org/draft/2019-09). +Better JSON Schema Errors works with **any JSON Schema validator** that follows the official [JSON Schema Output Format](https://json-schema.org/draft/2020-12/json-schema-core#name-output-structure). In this example, we’ll showcase it with the [Hyperjump JSON Schema Validator](https://github.com/hyperjump-io/json-schema). Now let's define a schema and some invalid data, then run the validation and process the output with `better-json-schema-errors`. :- @@ -46,29 +46,26 @@ Now let's define a schema and some invalid data, then run the validation and pro import { registerSchema, validate, unregisterSchema } from "@hyperjump/json-schema/draft-2020-12"; import { betterJsonSchemaErrors } from "@hyperjump/better-json-schema-errors"; -async function runExample() { - const schemaUri = "https://example.com/main"; - registerSchema({ - $schema: "https://json-schema.org/draft/2020-12/schema", - anyOf: [ - { enum: ["a", "b", "c"] } - ] - }, schemaUri); - - const instance = 4; - const result = await validate(schemaUri, instance, "BASIC"); - - if (result.valid) { - console.log("Valid instance!"); - } else { - const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance); - console.log(JSON.stringify(friendlyErrors, null, 2)); - } - - unregisterSchema(schemaUri); +const schemaUri = "https://example.com/main"; +registerSchema({ + $schema: "https://json-schema.org/draft/2020-12/schema", + anyOf: [ + { enum: ["a", "b", "c"] } + ] +}, schemaUri); + +const instance = 4; +const result = await validate(schemaUri, instance, "BASIC"); + +if (result.valid) { + console.log("Valid instance!"); +} else { + const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance); + console.log(JSON.stringify(friendlyErrors, null, 2)); } -await runExample(); +unregisterSchema(schemaUri); + ``` Output:- ```json @@ -84,7 +81,7 @@ Output:- ``` ## Features and Advanced Usage -Better JSON Schema Errors goes beyond simply rephrasing validation messages. It provides a **robust error normalization layer** on top of the official [JSON Schema Output Format](https://json-schema.org/draft/2019-09), ensuring consistent, human-friendly results across different validators and error formats. + ### 1. Works with All Output Formats Supports all three standardized output formats: - **BASIC** — The "Basic" structure is a flat list of output units @@ -95,10 +92,7 @@ schema. No matter which output format your validator produces, Better JSON Schema Errors can process it. -### 2. Resolves Output Ambiguities -Some validators may provide inconsistent or ambiguous data, such as using `keywordLocation` instead of the required `absoluteKeywordLocation`. Our library resolves these issues by normalizing both the `keywordLocation` and `instanceLocation`. It adds the leading `#` to `instanceLocation` and standardizes the path, so everything is a valid JSON Pointer and can be used predictably. This helps resolve ambiguities and provides a unified, reliable format for all error messages. - -### 3. Multiple Schema Locations +### 2. Multiple Schema Locations Sometimes a single validation issue is tied to **more than one schema keyword**. For example, when both `minimum` and `exclusiveMinimum` apply, or when `minLength` and `maxLength` constraints overlap or when when both `minimum` and `maximum` apply. @@ -114,138 +108,22 @@ Instead of generating duplicate error messages, It groups these into an **array "message": "Expected a number greater than 5 and less than 10." } ``` -### 4. Localization +### 3. Localization -The library uses a [fluent/bundle](https://github.com/projectfluent/fluent.js/tree/main/fluent-bundle) to provide localized error messages. By default, only English is supported. +The library uses [fluent](https://projectfluent.org) `.ftl` files provide localized error messages. By default, only English is supported. We need contributions from different countries to add more languages. To change the language, pass a language option to the betterJsonSchemaErrors function, like this: ``` - const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance, "en-US"); -``` -### 5. Handling `anyOf` with Clarity - -The `anyOf` keyword is a powerful but complex JSON Schema feature. When an instance fails to validate against all subschemas within an `anyOf`, standard validator output can be verbose and confusing, often returning an error for each failed subschema. - -**Better JSON Schema Errors** intelligently simplifies this output, providing clear, consolidated error messages that are easier to debug. - ---- - -#### 1. Mismatched Types - -If the instance's type doesn't match any of the alternatives in an `anyOf`, the library provides a concise error message listing the expected types. - -**Schema:** -```json -{ - "anyOf": [ - { "type": "string" }, - { "type": "number" } - ] -} -``` - -Invalid Instance:- -``` Json -false -``` -BetterJSONSchemaErrors Output:- -``` Json -{ - "errors": [ - { - "schemaLocation": "https://example.com/main#/anyOf", - "instanceLocation": "#", - "message": "The instance should be of type 'string' or 'number' but found 'boolean'." - } - ] -} - +const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance, { language: "en-US" }); ``` -#### 2. Partial Match with a Failed Constraint -If the instance's type matches one of the `anyOf` alternatives but fails a subsequent constraint (like `minLength`), our library correctly identifies the specific rule that was violated. +### 4. Handling `anyOf` with Clarity -**Schema:** -```json -{ - "anyOf": [ - { "type": "string", "minLength": 5 }, - { "type": "number" } - ] -} -``` +The `anyOf` keyword is a powerful but complex JSON Schema feature. **Better-JSON-Schema-Errors** intelligently simplifies its output by providing clear, consolidated error messages that are easier to debug. For detailed examples, see the dedicated [**anyOf** documentation](./docs/anyOf.md). -Invalid Instance:- -``` Json -"abc" -``` -BetterJSONSchemaErrors Output:- -``` Json -{ - "errors": [ - { - "schemaLocation": "https://example.com/main#/anyOf/0/minLength", - "instanceLocation": "#", - "message": "Expected a string at least 5 characters long." - } - ] -} - -``` - -#### 3. Multiple types match, pick based on field overlap - -When an instance matches multiple `anyOf` alternatives type, the library prioritizes the one with the most relevant error based on the instance's fields. - -**Schema:** -```json -{ - "anyOf": [ - { - "type": "object", - "properties": { - "name": { "type": "string" }, - "age": { "type": "number" } - }, - "required": ["name", "age"] - }, - { - "type": "object", - "properties": { - "title": { "type": "string" }, - "author": { "type": "string" }, - "ID": { "type": "string", "pattern": "^[0-9\\-]+$" } - }, - "required": ["title", "author", "ID"] - } - ] -} -``` - -Invalid Instance:- -``` Json -{ - "title": "Clean Code", - "author": "Robert Martin", - "ID": "NotValidId" -} -``` -BetterJSONSchemaErrors Output:- -``` Json -{ - "errors": [ - { - "schemaLocation": "https://example.com/main#/anyOf/1/properties/ID/pattern", - "instanceLocation": "#/ID", - "message": "The instance should match the format: \"^[0-9\\-]+$\". " - } - ] -} - -``` ## API diff --git a/docs/anyOf.md b/docs/anyOf.md new file mode 100644 index 0000000..0fd9adb --- /dev/null +++ b/docs/anyOf.md @@ -0,0 +1,119 @@ +## Handling `anyOf` with Clarity + +**Better JSON Schema Errors** intelligently simplifies error output, providing clear, consolidated error messages that are easier to debug. +Here are the differnt cases and how better-json-schema-errors handles them to produces better errors. + +--- + +#### 1. Mismatched Types + +If the instance's type doesn't match any of the alternatives in an `anyOf`, the library provides a concise error message listing the expected types. + +**Schema:** +```json +{ + "anyOf": [ + { "type": "string" }, + { "type": "number" } + ] +} +``` + +Invalid Instance:- +``` Json +false +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf", + "instanceLocation": "#", + "message": "The instance should be of type 'string' or 'number' but found 'boolean'." + } + ] +} + +``` +#### 2. Partial Match with a Failed Constraint + +If the instance's type matches one of the `anyOf` alternatives but fails a subsequent constraint (like `minLength`), our library correctly identifies the specific rule that was violated. + +**Schema:** +```json +{ + "anyOf": [ + { "type": "string", "minLength": 5 }, + { "type": "number" } + ] +} +``` + +Invalid Instance:- +``` Json +"abc" +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf/0/minLength", + "instanceLocation": "#", + "message": "Expected a string at least 5 characters long." + } + ] +} + +``` + +#### 3. Multiple types match, pick based on field overlap + +When an instance matches multiple `anyOf` alternatives type, the library prioritizes the one with the most relevant error based on the instance's fields. + +**Schema:** +```json +{ + "anyOf": [ + { + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "number" } + }, + "required": ["name", "age"] + }, + { + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { "type": "string" }, + "ID": { "type": "string", "pattern": "^[0-9\\-]+$" } + }, + "required": ["title", "author", "ID"] + } + ] +} +``` + +Invalid Instance:- +``` Json +{ + "title": "Clean Code", + "author": "Robert Martin", + "ID": "NotValidId" +} +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf/1/properties/ID/pattern", + "instanceLocation": "#/ID", + "message": "The instance should match the format: \"^[0-9\\-]+$\". " + } + ] +} +``` \ No newline at end of file From 721b561090aa4b9b074b4175974c171b2273a56d Mon Sep 17 00:00:00 2001 From: Arpit Kuriyal Date: Tue, 19 Aug 2025 15:55:21 +0530 Subject: [PATCH 4/6] minor changes --- .gitignore | 2 +- README.md | 6 +++--- {docs => documentation}/anyOf.md | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename {docs => documentation}/anyOf.md (100%) diff --git a/.gitignore b/.gitignore index 833b127..902fd38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules/ - +/docs scratch/ TODO* .DS_Store \ No newline at end of file diff --git a/README.md b/README.md index a51021c..accb51c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ npm install @hyperjump/better-json-schema-errors ## API Error Message Format Our API Error Format includes :- -- **`schemaLocation`** - A JSON URI that points to the specific keyword(s) within the schema that failed validation. This can be a single string or an array of absolute keyword locations for errors that are grouped together. +- **`schemaLocation`** - A URI that points to the specific keyword(s) within the schema that failed validation. This can be a single string or an array of absolute keyword locations for errors that are grouped together. - **`instanceLocation`** - JSON Pointer to the invalid piece of input data. @@ -96,7 +96,7 @@ No matter which output format your validator produces, Better JSON Schema Errors Sometimes a single validation issue is tied to **more than one schema keyword**. For example, when both `minimum` and `exclusiveMinimum` apply, or when `minLength` and `maxLength` constraints overlap or when when both `minimum` and `maximum` apply. -Instead of generating duplicate error messages, It groups these into an **array of schema locations** and produces one concise, human-friendly message :- +Instead of multiple related error messages, It groups these into an **array of schema locations** and produces one concise, human-friendly message :- ```json { @@ -110,7 +110,7 @@ Instead of generating duplicate error messages, It groups these into an **array ``` ### 3. Localization -The library uses [fluent](https://projectfluent.org) `.ftl` files provide localized error messages. By default, only English is supported. +The library uses [fluent](https://projectfluent.org) `.ftl` files to provide localized error messages. By default, only English is supported. We need contributions from different countries to add more languages. diff --git a/docs/anyOf.md b/documentation/anyOf.md similarity index 100% rename from docs/anyOf.md rename to documentation/anyOf.md From cb725adbfab855486be98d021196cb210b311f39 Mon Sep 17 00:00:00 2001 From: Arpit Kuriyal Date: Tue, 19 Aug 2025 21:27:07 +0530 Subject: [PATCH 5/6] updated readme file --- README.md | 147 ++++++++++++++++++++++++++++++++- documentation/anyOf.md | 68 +++++++++++++++ documentation/enum.md | 62 ++++++++++++++ documentation/range-handler.md | 142 +++++++++++++++++++++++++++++++ src/error-handlers/anyOf.js | 2 +- 5 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 documentation/enum.md create mode 100644 documentation/range-handler.md diff --git a/README.md b/README.md index accb51c..97861e7 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,153 @@ const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance, ### 4. Handling `anyOf` with Clarity -The `anyOf` keyword is a powerful but complex JSON Schema feature. **Better-JSON-Schema-Errors** intelligently simplifies its output by providing clear, consolidated error messages that are easier to debug. For detailed examples, see the dedicated [**anyOf** documentation](./docs/anyOf.md). +The `anyOf` keyword is a powerful but complex JSON Schema feature. **Better-JSON-Schema-Errors** intelligently simplifies its output by providing clear, consolidated error messages that are easier to debug. +**Schema:** +```json +{ + "anyOf": [ + { "type": "string" }, + { "type": "number" } + ] +} +``` + +Invalid Instance:- +``` Json +false +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf", + "instanceLocation": "#", + "message": "The instance should be of type 'string' or 'number' but found 'boolean'." + } + ] +} +``` + +For detailed examples, see the dedicated [**anyOf** documentation](./documentation/anyOf.md). + +### 5. Handling `enum` with Suggestions + +When data doesn’t match an allowed `enum` value, Better JSON Schema Errors produces clear messages. +It can also suggest the closest valid value (using string similarity). + +Example: + +```json +{ + "errors": [ + { + "message": "Unexpected value 'appl'. Did you mean 'apple'?", + "instanceLocation": "#/fruit", + "schemaLocation": "https://example.com/main#/properties/fruit/enum" + } + ] +} +``` +This makes typos or near-misses much easier to debug. +For full details and strategies, see the dedicated [enum documentation](./documentation/enum.md). + +### 6. Range constraint keyword +Better JSON Schema Errors consolidates multiple range-related validation errors (`minimum`, `maxLength`, `minItems`, etc.) into a single, clear message. +For example, a schema like: +```json +{ "allOf": [ + { "minimum": 3 }, + { "minimum": 5 } + ] +} +``` +Instance:- +```Json +2 +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/allOf/1/minimum", + "instanceLocation": "#", + "message": "Expected a number greater than 5." + } + ] +} +``` +Instead of 2 error message it manage to give single concise error message. For details, see the dedicated [Range documenetation](./documentation/range-handler.md) + +### 6. Custom Keywords and Error Handlers +In order to create the custom keywords and error handlers we need create and register two types of handler: **Normalization Handler** and **Error Handlers** + +1. Normalization: This phase takes the raw, often deeply nested, error tree from the validator and converts it into a NormalizedOutput (can check type of normalizedOutput in the index.d.ts file). + +2. Error Handling: This phase takes the normalized data and uses it to generate the final error messages. This is the job of the Error Handlers. + +Fist step -: Creating the keywordHandler +```Js +/** + * @import { KeywordHandler } from "@hyperjump/better-json-schema-errors" + */ + +/** @type KeywordHandler */ +const multipleOfTen = { + appliesTo(type) { + return type === "number" + } +}; + +export default multipleOfTen; + +``` + +Second step -: Creating the errorHandler +```Js +import { getSchema } from "@hyperjump/json-schema/experimental"; +import * as Schema from "@hyperjump/browser"; +import * as Instance from "@hyperjump/json-schema/instance/experimental"; + +/** + * @import { ErrorHandler, ErrorObject } from "@hyperjump/better-json-schema-errors" + */ + +/** @type ErrorHandler */ +const ErrorHandler = async (normalizedErrors, instance, localization) => { + /** @type ErrorObject[] */ + const errors = []; + + if (normalizedErrors["https://json-schema.org/keyword/multipleOfTen"]) { + for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/multipleOfTen"]) { + if (!normalizedErrors["https://json-schema.org/keyword/multipleOfTen"][schemaLocation]) { + errors.push({ + message: "Instance must be multiple of 10", + instanceLocation: Instance.uri(instance), + schemaLocation: schemaLocation + }); + } + } + } + + return errors; +}; + + +``` + +Step 3:- Register the handlers: + +```Js +import { setNormalizationHandler, addErrorHandler } from "@hyperjump/better-json-schema-errors"; +const KEYWORD_URI = "https://example.com/keyword/multipleOfTen"; + +setNormalizationHandler(CUSTOM_KEYWORD_URI, multipleOften); + +addErrorHandler(errorHandler); +``` ## API diff --git a/documentation/anyOf.md b/documentation/anyOf.md index 0fd9adb..39b05e2 100644 --- a/documentation/anyOf.md +++ b/documentation/anyOf.md @@ -116,4 +116,72 @@ BetterJSONSchemaErrors Output:- } ] } +``` + +#### 4. anyOf handling const/enum + +When an instance fails all enum or const options in an anyOf, the library merges them into one clear error message, avoiding multiple errors and even suggesting close matches in many cases. + +**Schema:** +```json +{ + "anyOf": [ + { "enum": ["a", "b", "c"] }, + { "const": 1 } + ] +} +``` + +Invalid Instance:- +``` Json +2 +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf", + "instanceLocation": "#", + "message": "Unexpected value 2. Expected one of: 'a', 'b', 'c' or 1 ." + } + ] +} + +``` + +#### 5. anyOf with a Discriminator + +When `anyOf` uses a discriminator, the library leverages it to give precise errors, matching the instance to the intended alternative via a specific property (e.g., `"type"`, `"const"`). + + +**Schema:** +```json +{ + "anyOf": [ + { "properties": { "type": { "const": "a" }, "apple": { "type": "string" } } }, + { "properties": { "type": { "const": "b" }, "banana": { "type": "string" } } } + ] +} +``` + +Invalid Instance:- +``` Json +{ + "type": "a", + "apple": 42 +} +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/anyOf/0/properties/apple/type", + "instanceLocation": "#/apple", + "message": "The instance should be of type 'string' but found 'number'." + } + ] +} + ``` \ No newline at end of file diff --git a/documentation/enum.md b/documentation/enum.md new file mode 100644 index 0000000..c658100 --- /dev/null +++ b/documentation/enum.md @@ -0,0 +1,62 @@ +# Handling `enum` in Better JSON Schema Errors + +The `enum` keyword restricts a value to a fixed set of allowed options. +This library enhances `enum` errors with **two strategies**: + +--- + +## 1. Suggestion Strategy (Levenshtein Distance) + +If the user-provided value is close to one of the allowed values (based on string similarity), +the error message will include a **suggestion**. + +Example Schema: +**Schema:** +```json +{ + "type": "string", + "enum": ["apple", "banana", "orange"] +} +``` + +Invalid Instance:- +``` Json +{ "fruit": "appl" } +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errros": { + "message": "Unexpected value 'appl'. Did you mean 'apple'?", + "instanceLocation": "#/fruit", + "schemaLocation": "https://example.com/main#/properties/fruit/enum" + } +} +``` +## 2. Fallback Strategy (List All Options) + +If no close match is found, the error lists all valid values: + +Example Schema: +**Schema:** +```json +{ + "type": "string", + "enum": ["apple", "banana", "orange"] +} +``` + +Invalid Instance:- +``` Json +{ "fruit": "grape" } +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errros": { + "message": "Unexpected value 'grape'. Expected one of: 'apple', 'banana', or 'orange'.", + "instanceLocation": "#/fruit", + "schemaLocation": "https://example.com/main#/properties/fruit/enum" + } +} +``` \ No newline at end of file diff --git a/documentation/range-handler.md b/documentation/range-handler.md new file mode 100644 index 0000000..6953f0b --- /dev/null +++ b/documentation/range-handler.md @@ -0,0 +1,142 @@ +# Range Constraint Keywords + +better-json-schema-errros processes validation errors for keywords that define a numeric range or count, such as `minimum`/`maximum`/`exclusiveMinimum`/`exculsiveMaximum` for numbers, `minLength`/`maxLength` for strings, `minItems`/`maxItems` for arrays, and `minProperties`/`maxProperties` for objects. +The primary goal is to consolidate multiple, separate validation errors related to these constraints into a single, clear, and human-readable message that describes the effective valid range for the instance. + +--- + +### Explaination + +When a JSON schema uses combinators like `allOf`, it's possible for an instance to fail validation against multiple range constraints simultaneously. For example, a schema might require an array to have at least 3 items and also at least 5 items. A standard validator might produce two separate errors for this. + +This handler improves the user experience by implementing a unified consolidation strategy for all data types: + +1. Collect All Range Failures: It gathers all failed validation errors for a given type (e.g., all minItems and maxItems failures for an array). + +2. Determine the Strictest Bounds: + + - It calculates the most restrictive lower bound by finding the highest min value (e.g., minimum: 5 is stricter than minimum: 3). + + - It calculates the most restrictive upper bound by finding the lowest max value (e.g., maxLength: 10 is stricter than maxLength: 20). + + - It correctly tracks exclusivity for numbers (e.g., exclusiveMinimum). + +3. Produce a Single, Unified Error: After calculating the effective range, it generates a single error message that combines these constraints into one statement. This prevents overwhelming the user with redundant information and clearly states what is allowed. + +### Examples +### 1. **Number** (`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`) +Schema: +```Json +{ + "allOf": [ + { "minimum": 3 }, + { "minimum": 5 } + ] +} +``` +Instance:- +```Json +2 +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/allOf/1/minimum", + "instanceLocation": "#", + "message": "Expected a number greater than 5." + } + ] +} + +``` + +### 2. **String** (`minLength`, `maxLength`) +Schema: +```Json +{ + "allOf": [ + { "minLength": 3 }, + { "minLegth": 5 } + ] +} +``` +Instance:- +```Json +"helo" +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": "https://example.com/main#/allOf/1/minLength", + "instanceLocation": "#", + "message": "Expected a string at least 5 characters long" + } + ] +} + +``` + +### 3. **Array** (`minItems`, `maxItems`) +Schema: +```Json +{ + "allOf": [ + { "minItems": 3 }, + { "maxItems": 7 } + ] +} +``` +Instance:- +```Json +[1,2] +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": [ + "https://example.com/main#/allOf/0/minItems", + "https://example.com/main#/allOf/1/maxItems" + ], + "instanceLocation": "#", + "message": "Expected the array to have at least 2 items and at most 7 items." + } + ] +} + +``` +### 4. **Object** (`minProperties`, `maxProperties`) +Schema: +```Json +{ + "allOf": [ + { "minProperties": 2 }, + { "maxProperties": 5 } + ] +} +``` +Instance:- +```Json +{"a": 1} +``` +BetterJSONSchemaErrors Output:- +``` Json +{ + "errors": [ + { + "schemaLocation": [ + "https://example.com/main#/allOf/0/minProperteis", + "https://example.com/main#/allOf/1/maxProperties" + ], + "instanceLocation": "#", + "message": "Expected the object to have at least 2 properties and at most 5 Properties." + } + ] +} + +``` \ No newline at end of file diff --git a/src/error-handlers/anyOf.js b/src/error-handlers/anyOf.js index 77244a9..14d14d0 100644 --- a/src/error-handlers/anyOf.js +++ b/src/error-handlers/anyOf.js @@ -5,7 +5,7 @@ import * as JsonPointer from "@hyperjump/json-pointer"; import { getErrors } from "../error-handling.js"; /** - * @import { ErrorHandler, ErrorObject, NormalizedOutput } from "../index.d.ts" + * @import { ErrorHandler, ErrorObject, Json, NormalizedOutput } from "../index.d.ts" */ /** @type ErrorHandler */ From aff5e526ae4a443aa315439fb091c1e2af7395b1 Mon Sep 17 00:00:00 2001 From: Arpit Kuriyal Date: Tue, 19 Aug 2025 21:53:45 +0530 Subject: [PATCH 6/6] updated readme file and added new files in documentation folder --- .gitignore | 2 +- README.md | 47 +++++++++++++++++----------------- documentation/anyOf.md | 37 ++++++++++++++++---------- documentation/enum.md | 10 +++----- documentation/range-handler.md | 28 ++++++++++---------- 5 files changed, 66 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 902fd38..b50d613 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules/ - /docs + scratch/ TODO* .DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 97861e7..911e969 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ This module is designed for node.js (ES Modules, TypeScript) and browsers. It should work in Bun and Deno as well, but the test runner doesn't work in these environments, so this module may be less stable in those environments. - - ## Examples and Basic Usage Better JSON Schema Errors works with **any JSON Schema validator** that follows the official [JSON Schema Output Format](https://json-schema.org/draft/2020-12/json-schema-core#name-output-structure). In this example, we’ll showcase it with the [Hyperjump JSON Schema Validator](https://github.com/hyperjump-io/json-schema). @@ -116,36 +114,36 @@ We need contributions from different countries to add more languages. To change the language, pass a language option to the betterJsonSchemaErrors function, like this: -``` +```js const friendlyErrors = await betterJsonSchemaErrors(result, schemaUri, instance, { language: "en-US" }); ``` -### 4. Handling `anyOf` with Clarity +### 4. Handling `anyOf`/`oneOf` with Clarity -The `anyOf` keyword is a powerful but complex JSON Schema feature. **Better-JSON-Schema-Errors** intelligently simplifies its output by providing clear, consolidated error messages that are easier to debug. +The `anyOf`/`oneOf` keyword is a powerful but complex JSON Schema feature. **better-json-schema-errors** intelligently simplifies its output by providing clear, consolidated error messages that are easier to debug. Whenever possible it will try to determine which alternative the user intended and focus the error output to only those errors related to correcting the data for that alternative. **Schema:** ```json { "anyOf": [ - { "type": "string" }, + { "type": "string", "minLength": 5 }, { "type": "number" } ] } ``` Invalid Instance:- -``` Json -false +```json +"abc" ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { - "schemaLocation": "https://example.com/main#/anyOf", + "schemaLocation": "https://example.com/main#/anyOf/0/minLength", "instanceLocation": "#", - "message": "The instance should be of type 'string' or 'number' but found 'boolean'." + "message": "Expected a string at least 5 characters long." } ] } @@ -174,7 +172,7 @@ Example: This makes typos or near-misses much easier to debug. For full details and strategies, see the dedicated [enum documentation](./documentation/enum.md). -### 6. Range constraint keyword +### 6. Range constraint keywords Better JSON Schema Errors consolidates multiple range-related validation errors (`minimum`, `maxLength`, `minItems`, etc.) into a single, clear message. For example, a schema like: ```json @@ -185,11 +183,11 @@ For example, a schema like: } ``` Instance:- -```Json +```json 2 ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -200,17 +198,20 @@ BetterJSONSchemaErrors Output:- ] } ``` -Instead of 2 error message it manage to give single concise error message. For details, see the dedicated [Range documenetation](./documentation/range-handler.md) +Instead of 2 error message it manages to give a single concise error message. For details, see the dedicated [Range documenetation](./documentation/range-handler.md) ### 6. Custom Keywords and Error Handlers -In order to create the custom keywords and error handlers we need create and register two types of handler: **Normalization Handler** and **Error Handlers** +In order to create the custom keywords and error handlers we need to create and +register two types of handlers: **Normalization Handler** and **Error Handlers**. -1. Normalization: This phase takes the raw, often deeply nested, error tree from the validator and converts it into a NormalizedOutput (can check type of normalizedOutput in the index.d.ts file). +1. Normalization: This phase takes the raw, often deeply nested, error output +from the validator and converts it into a NormalizedOutput (you can check type of +normalizedOutput in the index.d.ts file). -2. Error Handling: This phase takes the normalized data and uses it to generate the final error messages. This is the job of the Error Handlers. +2. Error Handling: This phase takes the normalized output and uses it to generate the final error messages. This is the job of the Error Handlers. Fist step -: Creating the keywordHandler -```Js +```js /** * @import { KeywordHandler } from "@hyperjump/better-json-schema-errors" */ @@ -227,7 +228,7 @@ export default multipleOfTen; ``` Second step -: Creating the errorHandler -```Js +```js import { getSchema } from "@hyperjump/json-schema/experimental"; import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; @@ -261,11 +262,11 @@ const ErrorHandler = async (normalizedErrors, instance, localization) => { Step 3:- Register the handlers: -```Js +```js import { setNormalizationHandler, addErrorHandler } from "@hyperjump/better-json-schema-errors"; -const KEYWORD_URI = "https://example.com/keyword/multipleOfTen"; +const KEYWORD_URI = "https://json-schema.org/keyword/multipleOfTen"; -setNormalizationHandler(CUSTOM_KEYWORD_URI, multipleOften); +setNormalizationHandler(KEYWORD_URI, multipleOften); addErrorHandler(errorHandler); ``` diff --git a/documentation/anyOf.md b/documentation/anyOf.md index 39b05e2..4d5f76a 100644 --- a/documentation/anyOf.md +++ b/documentation/anyOf.md @@ -1,6 +1,6 @@ ## Handling `anyOf` with Clarity -**Better JSON Schema Errors** intelligently simplifies error output, providing clear, consolidated error messages that are easier to debug. +`better-json-schema-errors` intelligently simplifies error output, providing clear, consolidated error messages that are easier to debug. Here are the differnt cases and how better-json-schema-errors handles them to produces better errors. --- @@ -20,11 +20,11 @@ If the instance's type doesn't match any of the alternatives in an `anyOf`, the ``` Invalid Instance:- -``` Json +```json false ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -51,11 +51,11 @@ If the instance's type matches one of the `anyOf` alternatives but fails a subse ``` Invalid Instance:- -``` Json +```json "abc" ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -98,7 +98,7 @@ When an instance matches multiple `anyOf` alternatives type, the library priorit ``` Invalid Instance:- -``` Json +```json { "title": "Clean Code", "author": "Robert Martin", @@ -106,7 +106,7 @@ Invalid Instance:- } ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -133,11 +133,11 @@ When an instance fails all enum or const options in an anyOf, the library merges ``` Invalid Instance:- -``` Json +```json 2 ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -159,21 +159,31 @@ When `anyOf` uses a discriminator, the library leverages it to give precise erro ```json { "anyOf": [ - { "properties": { "type": { "const": "a" }, "apple": { "type": "string" } } }, - { "properties": { "type": { "const": "b" }, "banana": { "type": "string" } } } + { + "properties": { + "type": { "const": "a" }, + "apple": { "type": "string" } + } + }, + { + "properties": { + "type": { "const": "b" }, + "banana": { "type": "string" } + } + } ] } ``` Invalid Instance:- -``` Json +```json { "type": "a", "apple": 42 } ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -183,5 +193,4 @@ BetterJSONSchemaErrors Output:- } ] } - ``` \ No newline at end of file diff --git a/documentation/enum.md b/documentation/enum.md index c658100..85394a4 100644 --- a/documentation/enum.md +++ b/documentation/enum.md @@ -14,17 +14,16 @@ Example Schema: **Schema:** ```json { - "type": "string", "enum": ["apple", "banana", "orange"] } ``` Invalid Instance:- -``` Json +```json { "fruit": "appl" } ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errros": { "message": "Unexpected value 'appl'. Did you mean 'apple'?", @@ -41,17 +40,16 @@ Example Schema: **Schema:** ```json { - "type": "string", "enum": ["apple", "banana", "orange"] } ``` Invalid Instance:- -``` Json +```json { "fruit": "grape" } ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errros": { "message": "Unexpected value 'grape'. Expected one of: 'apple', 'banana', or 'orange'.", diff --git a/documentation/range-handler.md b/documentation/range-handler.md index 6953f0b..a4cebf2 100644 --- a/documentation/range-handler.md +++ b/documentation/range-handler.md @@ -1,6 +1,6 @@ # Range Constraint Keywords -better-json-schema-errros processes validation errors for keywords that define a numeric range or count, such as `minimum`/`maximum`/`exclusiveMinimum`/`exculsiveMaximum` for numbers, `minLength`/`maxLength` for strings, `minItems`/`maxItems` for arrays, and `minProperties`/`maxProperties` for objects. +`better-json-schema-errors` processes validation errors for keywords that define a numeric range or count, such as `minimum`/`maximum`/`exclusiveMinimum`/`exculsiveMaximum` for numbers, `minLength`/`maxLength` for strings, `minItems`/`maxItems` for arrays, and `minProperties`/`maxProperties` for objects. The primary goal is to consolidate multiple, separate validation errors related to these constraints into a single, clear, and human-readable message that describes the effective valid range for the instance. --- @@ -26,7 +26,7 @@ This handler improves the user experience by implementing a unified consolidatio ### Examples ### 1. **Number** (`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`) Schema: -```Json +```json { "allOf": [ { "minimum": 3 }, @@ -35,11 +35,11 @@ Schema: } ``` Instance:- -```Json +```json 2 ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -54,7 +54,7 @@ BetterJSONSchemaErrors Output:- ### 2. **String** (`minLength`, `maxLength`) Schema: -```Json +```json { "allOf": [ { "minLength": 3 }, @@ -63,11 +63,11 @@ Schema: } ``` Instance:- -```Json +```json "helo" ``` -BetterJSONSchemaErrors Output:- -``` Json +`better-json-schema-errors` Output:- +```json { "errors": [ { @@ -82,7 +82,7 @@ BetterJSONSchemaErrors Output:- ### 3. **Array** (`minItems`, `maxItems`) Schema: -```Json +```json { "allOf": [ { "minItems": 3 }, @@ -91,11 +91,11 @@ Schema: } ``` Instance:- -```Json +```json [1,2] ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ { @@ -112,7 +112,7 @@ BetterJSONSchemaErrors Output:- ``` ### 4. **Object** (`minProperties`, `maxProperties`) Schema: -```Json +```json { "allOf": [ { "minProperties": 2 }, @@ -121,11 +121,11 @@ Schema: } ``` Instance:- -```Json +```json {"a": 1} ``` BetterJSONSchemaErrors Output:- -``` Json +```json { "errors": [ {