deep-json-validation
v1.0.22
Published
Validate complex JSON objects with deep structure
Readme
For OOP lovers and JavaScript/TypeScript typing addicts, deep-json-validation is here.
A variety of ways to validate your JSON from unknown sources or data structures in a safe, typed manner.
With Deep JSON Validation, you can:
- Validate complex JSON structures without limits on depth.
- Serialize your validations and save them to JSON.
- Deserialize your validations from JSON and rebuild your validation rules.
- Know the exact point where data validation fails.
- Obtain a safely typed JSON object after validation.
- Write validation rules easily, quickly, and super intuitively.
Below are examples of how to use Deep JSON Validation.
Documentation
Import the necessary components
import {
/** Validation */
JV,
JVAny,
JVArray,
JVBigInt,
JVBoolean,
JVClass,
JVCustom,
JVDate,
JVNode,
JVNumber,
JVSomeOf,
JVString,
/** Errors */
JVError,
JVKeyError,
JVKeyRegexError,
JVKeyRequiredError,
JVKeyTypeError,
/** Navigation */
JN
} from 'deep-json-validation';
Let's create a few functions for convenience and to better demonstrate how Deep JSON Validation works.
class Response {
private code: number = 200;
private data: any = {}
constructor() { }
status(code: number): this {
this.code = code;
return this;
}
json(data: any): this {
this.data = data;
console.log('Response code:', this.code);
console.log('Response data:', JSON.stringify(this.data, null, 2));
return this;
}
end(): void {
console.log(`Status code: ${this.code}, data: `, this.data);
}
}Most common case: validating an HTTP request body.
We could have a "people" table with columns like
- name - string data type, required
- surname - string data type, required
- email - string data type, required in the body but nullable in the database
- phonenumber - string data type, not required
- dateOfBirth - Date data type, not required in the body and can be null if present
- hobby - an array of strings, the key is required but can be an empty array
- skills - an array of structured JSON, where each JSON has the keys "name" (string, required) and "level" (number, from 0 to 10, required)
- notes - an array of structured JSON, not required and cannot be null if present, where each JSON has the keys "text" (string, required) and "date" (Date, required)
- score - a number, not required in the body, but if present it must be between 0 and 100
async function createPersonMiddleware(req: { body: any }, res: Response, next: any = () => { console.log('Middleware passed successfully!') }): Promise<void> {
// Validation of this body with Deep JSON Validation would be:
const myJV = new JV()
.req('name', new JVString().regExp(/^[a-zA-Z'\s]{3,50}$/))
.require('surname', new JVString().regExp(/^[a-zA-Z'\s]{3,50}$/))
.req('email', new JVString().regExp(/^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$|^[a-z0-9]{5,12}$/).nullable())
.optional('phonenumber', new JVString().regExp(/^\+?\d{1,3}(?:\s?\d){5,12}$/))
.opt('dateOfBirth', new JVDate().nullable())
.req('hobby', new JVArray(new JVString().setRegex(/^.+$/)))
.req('skills', new JVArray(
new JVNode(
new JV()
.req('name', new JVString().regExp(/^.{1,30}$/))
.req('level', new JVNumber().setMin(0).setMax(10))
)
))
.opt('notes', new JVArray(
new JVNode(
new JV()
.req('text', new JVString().regExp(/^.{1,300}$/))
.req('date', new JVDate())
)
))
.opt('score', new JVNumber().min(0).max(100));
const result = myJV.validate(
/** The body to validate */
req.body,
/** If we want it to return a boolean in case of failed validation, otherwise an error will be thrown that extends JVError*/
false
);
if (!result)
res.status(400).json({ error: 'Invalid request body' }).end();
else
next();
}Same example as before, but this time we handle the error generated by JV
async function createPersonMiddlewareThrowError(req: { body: any }, res: Response, next: any = () => { console.log('Middleware passed successfully!') }): Promise<void> {
const myJV = new JV()
.req('name', new JVString().regExp(/^[a-zA-Z'\s]{3,50}$/))
.require('surname', new JVString().regExp(/^[a-zA-Z'\s]{3,50}$/))
.req('email', new JVString().regExp(/^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$|^[a-z0-9]{5,12}$/).nullable())
.optional('phonenumber', new JVString().regExp(/^\+?\d{1,3}(?:\s?\d){5,12}$/))
.opt('dateOfBirth', new JVDate().nullable())
.req('hobby', new JVArray(new JVString().setRegex(/^.+$/)))
.req('skills', new JVArray(
new JVNode(
new JV()
.req('name', new JVString().regExp(/^.{1,30}$/))
.req('level', new JVNumber().setMin(0).setMax(10))
)
))
.opt('notes', new JVArray(
new JVNode(
new JV()
.req('text', new JVString().regExp(/^.{1,300}$/))
.req('date', new JVDate())
)
))
.opt('score', new JVNumber().min(0).max(100));
const result = myJV.validate(
/** The body to validate */
req.body,
/** We specifically throw an error if validation fails */
true
);
/** We don't check the validation result, if it's invalid it will throw an error, so we'll handle the failure in the catch block */
next();
}Let's test our middleware with a valid body:
createPersonMiddleware({
body: {
name: "Dmytro",
surname: "Vydysh",
email: "[email protected]",
dateOfBirth: null,
hobby: ["Coding"],
skills: [
{ name: "TypeScript", level: 9 },
{ name: "ChatGPT", level: 10 }
],
notes: [
{ text: "Added new features to the deep-json-validation library", date: new Date("2025-09-23") },
{ text: "Fantastic person!", date: new Date("2025-09-23") },
],
score: 95
}
}, new Response());Output:
Middleware passed successfully!Let's instead try to make it fail with an invalid body:
createPersonMiddleware({
body: {
name: "Dmytro123", // Invalid
surname: "Vydysh",
email: "[email protected]",
phonenumber: "1234" // Invalid
}
}, new Response());Output:
Status code: 400, data: { error: 'Invalid request body' }What if we wanted to know exactly where the body failed validation?
We need to make JV throw an error if validation fails.
(async () => {
try {
await createPersonMiddlewareThrowError({
body: {
name: "Dmytro", // invalid
surname: "Vydysh",
email: "[email protected]",
phonenumber: "1234" // also invalid
}
}, new Response());
} catch (e) {
if ((e as Error) instanceof JVError)
console.error('Validation failed. Details:', JV.error(e as JVError));
else console.error(e);
// Here the handling of the failed request, obviously
}
})()Output:
Validation failed. Details: {
message: `The value "Dmytro123" does not match the regex "^[a-zA-Z'\s]{3,50}$".`,
address: 'name'
}Same thing for the phone number
Output:
Validation failed. Details: {
message: 'The value "1234" does not match the regex "^\\+?\\d{1,3}(?:\s?\\d){5,12}$".',
address: 'phonenumber'
}The static "error" method of the JV class retrieves the error message, which is in the form:
Output:
JVKeyRegexError: [DEEP JSON VALIDATION] JSON validation failed!
Error: <JVError>The value "Dmytro123" does not match the regex "^[a-zA-Z'\s]{3,50}$".</JVError>
Key address: nameIt extracts the necessary data and, if successful, returns an object with the following properties:
- message: the error message
- address: the address of the key that caused the error
Validation fails on the first error encountered, so if there are multiple errors, you'll find them iteratively.
Let's see below all the static and non-static methods of the JV class that can be useful to us.
// Create a new JV instance
const newJV = new JV();
Method require() & req()
It is used to make a key mandatory in the JSON you want to validate.
If the key is not present, validation fails.
newJV.require(
/** The name of the key that must be present in my JSON */
'key0',
/** The data type that must be respected */
new JVString()
);
The alias "req" version is also available
newJV.req('key1', new JVNumber());
Method optional() & opt()
It is used to make a key optional in the JSON you want to validate.
If the key is present, it must match the specified data type, otherwise validation fails.
If the key is not present, validation still passes.
newJV.optional(
/** The name of the key that can be present in my JSON */
'key2',
/** The data type it must match, if any */
new JVBoolean()
);The alias "opt" version is also available
newJV.opt('key3', new JVAny());Method example()
It is used to generate a JSON sample that meets the defined validation rules.
Useful for testing validation rules or for obtaining a valid JSON sample.
It returns a JSON object with sample values for each defined key.
console.log(newJV.example());Output:
{
key0: 'a string value',
key1: 2025,
key2: false,
key3: 'Any serializable value, including null.'
}Method exampleWithRules()
It is used to generate JSON with descriptive strings for the defined validation rules.
Useful for quick documentation.
console.log(newJV.exampleWithRules());{
key0: 'A string, Has no specific regex, Cannot be null.',
key1: 'A number, Cannot be null.',
key2: 'A boolean, Cannot be null.',
key3: 'Any serializable value'
}Method path()
Returns the structure of the JSON we want to validate, with the addresses of each key present in the JSON.
/** A short operation to better illustrate the idea */
newJV.req('key4', new JVArray(new JVNode(new JV().req('subKey1', new JVBigInt()).opt('subKey2', new JVDate()))));
console.log(newJV.path());Output:
{
key0: 'key0',
key1: 'key1',
key2: 'key2',
key3: 'key3',
'key4[]': { subKey1: 'key4/subKey1', subKey2: 'key4/subKey2' }
}Static method schema()
It is used to create JavaScript schemas from JSON.
Somewhat limiting, but useful with simple data types like strings, arrays, numbers, Booleans, and dates.
It does not support complex data types like custom classes or custom validations.
Useful for quickly creating a basic validation schema.
const myJVFromSchema = JV.schema({
name: 'Dmytro',
surname: 'Vydysh',
dateOfBirth: new Date('1990-01-01'),
});
console.log(myJVFromSchema.example());Output:
{
name: 'a string value',
surname: 'a string value',
dateOfBirth: '22/09/2025'
}Method removeKey()
It is used to remove a key from the JV validation schema.
It is useful for dynamically modifying the validation schema based on certain conditions.
newJV.removeKey('key4');Method json()
It is used to serialize the JV validation schema into a JSON that we can then store or perform other operations.
The JSON produced can later be used to reconstruct the original JV validation schema.
Warning: If you used real classes when using the JVClass validators,
or if you used real functions when using JVCustom, an error will be generated
because functions or classes cannot be serialized to JSON (that is, they can be serialized, but not deserialized).
To serialize JVClass or JVCustom, follow the documentation at the bottom.
console.dir(newJV.json(), { depth: null });Output:
{
type: 'object',
keys: [
{
name: 'key0',
required: true,
config: { type: 'string', null: false, regex: 'undefined', enum: null }
},
{
name: 'key1',
required: true,
config: {
type: 'number',
null: false,
min: undefined,
max: undefined,
enum: null
}
},
{
name: 'key2',
required: false,
config: { type: 'boolean', null: false }
},
{
name: 'key3',
required: false,
config: { type: 'any', null: true }
}
]
}Static method fromJSON()
It is used to reconstruct a JV validation schema from a JSON produced by the json() method.
const jvFromJSON = JV.fromJSON(newJV.json());Static method error()
It is used to extract validation error data quickly and easily.
console.log(JV.error(new JVError(`JVKeyRegexError: [DEEP JSON VALIDATION] JSON validation failed! Error: <JVError>The value "Dmytro123" does not match the regex "^[a-zA-Z'\s]{3,50}$".</JVError> Key address: name`)));
Output:
{
message: `The value "Dmytro123" does not match the regex "^[a-zA-Z's]{3,50}$".`,
address: 'name'
}Method validate()
It is used to validate JSON against the defined JV validation schema.
It returns true if the JSON is valid; otherwise, if the second parameter is true (as it is by default), it throws a JVError with the details of the failed validation.
const valid = newJV.validate({}, false/**Non lanciare l'errore */);Static method registerClass()
It allows you to globally register a class and then serialize the schema that JVClass uses.
class AnyClass { }
JV.registerClass('my-class-name', AnyClass);
console.dir(new JV().req('class', new JVClass('my-class-name')).json(), { depth: null });Output:
{
type: 'object',
keys: [
{
name: 'class',
required: true,
config: { type: 'class', class: 'my-class-name', null: false }
}
]
}If I do this without going through JV.registerClass, however, a serialization error will be generated
console.dir(new JV().req('class', new JVClass({ class: AnyClass })).json(), { depth: null });
Output:
JVKeyError: You cannot serialize a class key using real classes.
Static method removeClass()
Allows you to remove registered classes from the global JV class map for JVClass
JV.removeClass('my-class-name');Static method registerCustom()
It allows you to globally register a custom validation function so you can then serialize the schema using JVCustom.
JV.registerCustom('my-custom-function', function (value: any) { return true });
console.dir(new JV().req('custom', new JVCustom('my-custom-function')).json(), { depth: null });
``` typescript
Output:
``` bash
{
type: 'object',
keys: [
{
name: 'custom',
required: true,
config: {
type: 'custom',
callback: 'my-custom-function',
null: false
}
}
]
}If I do this without going through JV.registerCustom, however, a serialization error will be generated
console.dir(new JV().req('custom', new JVCustom(function (value: any) { return true })).json(), { depth: null });
Output:
JVKeyError: You cannot serialize a direct custom validator in JSON.Static method removeCustom()
Allows you to remove registered custom validation functions from the global JV function map for JVCustom
JV.removeCustom('my-custom-function');List of all JV validators with their methods
String validator
const jvString = new JVString()
/** Set a regex for validation */
.setRegex(/.*/)
/** Alias of "setRegex" */
.regExp(/.*/)
/** Set a list of allowed values */
.setEnum(['a', 'b', 'c'])
/** Alias of "setEnum" */
.enum(['a', 'b', 'c'])
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();Number Validator
const jvNumber = new JVNumber()
/** Set the minimum allowed value */
.setMin(0)
/** Alias of "setMin" */
.min(0)
/** Set the maximum allowed value */
.setMax(100)
/** Alias of "setMax" */
.max(100)
/** Set a list of allowed values */
.setEnum([0, 1, 2, 3, 4, 5])
/** Alias of "setEnum" */
.enum([0, 1, 2, 3, 4, 5])
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();
Boolean validator
const jvBoolean = new JVBoolean()
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();
BigInt validator
const jvBigInt = new JVBigInt()
/** Set the minimum allowed value */
.setMin(BigInt(0))
/** Alias of "setMin" */
.min(BigInt(0))
/** Set the maximum allowed value */
.setMax(BigInt(100))
/** Alias of "setMax" */
.max(BigInt(100))
/** Set a list of allowed values */
.setEnum([BigInt(0), BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)])
/** Alias of "setEnum" */
.enum([BigInt(0), BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)])
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();
Date validator
const jvDate = new JVDate()
/** Set a minimum allowed date */
.setMin(new Date('2000-01-01'))
/** Alias of "setMin" */
.min(new Date('2000-01-01'))
/** Set a maximum allowed date */
.setMax(new Date('2100-12-31'))
/** Alias of "setMax" */
.max(new Date('2100-12-31'))
/** Set up a list of allowed dates */
.setEnum([new Date('2000-01-01'), new Date('2000-01-02'), new Date('2000-01-03')])
/** Alias of "setEnum" */
.enum([new Date('2000-01-01'), new Date('2000-01-02'), new Date('2000-01-03')])
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();
Any validator
const jvAny = new JVAny();Array validator
As an argument it needs another JV validator that defines the data type of the array elements.
const jvArray = new JVArray(new JVString().setRegex(/^.+$/))
/** Sets the minimum length of the array */
.setMin(0)
/** Alias of "setMin" */
.min(0)
/** Set the maximum length of the array */
.setMax(100)
/** Alias of "setMax" */
.max(100);
Nested JSON Validator
As an argument it takes a JV instance that defines the structure of the nested JSON.
const jvNode = new JVNode(new JV().req('subKey1', new JVString()))
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();
Class validator
Check that the key value is an instance of the specified class
const jvClass = new JVClass({ class: Number })
/** Makes the value valid if null */
.setNull()
/** Alias of "setNull" */
.nullable();Or, using the global class map to take advantage of serialization
const jvClass2 = new JVClass('my-class-name')Custom validator
To validate use custom functions that must return a boolean value
const jvCustom = new JVCustom((v: any) => true)
Or, using the global function map to take advantage of serialization
const jvCustom2 = new JVCustom('my-custom-function');Multiple type validator
Accepts multiple data types, if one of the specified data types is valid, the validation passes.
const jvSomeOf = new JVSomeOf([new JVString(), new JVNumber(), new JVBoolean()]);
What is JN instead?
JN is a JSON navigation tool that allows you to easily navigate, recreate, extract, and modify data within JSON.
Below are existing methods with practical examples:
Static method path()
It is used to get the path of all the final keys present in a JSON.
Warning: This only works with string, number, boolean, and null values.
const pathed = JN.path({
key0: 'value0',
key1: 15,
key2: false,
key3: {
subKey1: 'subValue1',
subKey2: 25,
subKey3: {
subSubKey1: 'subSubValue1',
subSubKey2: null,
subSubKey3: [1, 2, 3]
}
}
})
console.dir(pathed, { depth: null });
Output:
{
key0: 'key0',
key1: 'key1',
key2: 'key2',
key3: {
subKey1: 'key3/subKey1',
subKey2: 'key3/subKey2',
subKey3: {
subSubKey1: 'key3/subKey3/subSubKey1',
subSubKey2: 'key3/subKey3/subSubKey2',
subSubKey3: [
'key3/subKey3/subSubKey3/0',
'key3/subKey3/subSubKey3/1',
'key3/subKey3/subSubKey3/2'
]
}
}
}
Static method pathWithValues()
It is used to get the path of all the final keys present in a JSON, along with their values.
Warning: This only works with string, number, boolean, and null values.
const pathedWithValues = JN.pathWithValues({
key0: 'value0',
key1: 15,
key2: false,
key3: {
subKey1: 'subValue1',
subKey2: 25,
subKey3: {
subSubKey1: 'subSubValue1',
subSubKey2: null,
subSubKey3: [1, 2, 3]
}
}
})
console.dir(pathedWithValues, { depth: null });
Output:
{
key0: { value: 'value0', path: 'key0' },
key1: { value: 15, path: 'key1' },
key2: { value: false, path: 'key2' },
key3: {
subKey1: { value: 'subValue1', path: 'key3/subKey1' },
subKey2: { value: 25, path: 'key3/subKey2' },
subKey3: {
subSubKey1: { value: 'subSubValue1', path: 'key3/subKey3/subSubKey1' },
subSubKey2: { value: null, path: 'key3/subKey3/subSubKey2' },
subSubKey3: [
{ value: 1, path: 'key3/subKey3/subSubKey3/0' },
{ value: 2, path: 'key3/subKey3/subSubKey3/1' },
{ value: 3, path: 'key3/subKey3/subSubKey3/2' }
]
}
}
}Static method get()
It is used to extract a value from a JSON, given its path.
const value = JN.get({
key0: 'value0',
key1: 15,
key2: false,
key3: {
subKey1: 'subValue1',
subKey2: 25,
subKey3: {
subSubKey1: 'subSubValue1',
subSubKey2: null,
subSubKey3: [1, 2, 3]
}
}
}, 'key3/subKey3/subSubKey3/2');
console.log(value);
Output:
3Static method materialize()
It is used to recreate a JSON from a JSON made of paths.
This is very useful when you have a very large and nested JSON input and you only want to extract a few specific keys.
const materialized = JN.materialize({
number_25: 'key3/subKey2',
number_3_from_array: 'key3/subKey3/subSubKey3/2',
boolean: 'key2',
string_subSubValue1: 'key3/subKey3/subSubKey1',
also: [
'key0',
'key1'
],
and_also_this: {
nested: 'key3/subKey1'
}
}, {
key0: 'value0',
key1: 15,
key2: false,
key3: {
subKey1: 'subValue1',
subKey2: 25,
subKey3: {
subSubKey1: 'subSubValue1',
subSubKey2: null,
subSubKey3: [1, 2, 3]
}
}
});
console.dir(materialized, { depth: null });
Output:
{
number_25: 25,
number_3_from_array: 3,
boolean: false,
string_subSubValue1: 'subSubValue1',
also: [ 'value0', 15 ],
and_also_this: { nested: 'subValue1' }
}Authors

Support
For support, email [email protected]
