Fluent JSON validator
An easy-to-use, expressive, and composable JSON object validator, with a fluent builder pattern interface!
// this is what you want to validate:
// coming from the user, read from a file, sent back by some API, etc.
const person = {
name: 'John Doe',
age: 42,
hobbies: ['eating', 'coding', 'sleeping'],
favoriteNumberOrColor: 'green'
};
// this is the structure you want your data to have:
const personSchema = is.Object({
name: is.String(),
nickname: is.optional().String(),
// ^ nickname may be missing, but if not, it must be a string.
age: is.Number().Which(
age => age > 10
// ^ age must be more than 10.
),
hobbies: is.ArrayOf(
is.String().Which(
hobby => hobby !== 'illegal activities'
// ^ hobbies can't include illegal activities!
)
),
favoriteNumberOrColor: is.OneOf([is.Number(), is.String()])
});
// and this is how you check if it matches or not:
personSchema.validate(person); // == true
Features
- Lightweight, since it has no dependencies!
- Has a small and simple API, only a handful of methods.
- Can validate primitive types, arrays (
is.ArrayOf
) and even variable types (is.OneOf
)! - Can validate any arbitrary JSON object structure, just mix'n'match the needed validators (
is.Object
)! - Schemas are reusable and composable for validating complex data structures painlessly!
- Can be used for formal1 and functional1 validation as well (
is.Which
)!
1: I may be using slightly incorrect words, but by formal validation I mean the subject has the desired structure, and by functional validation I mean the subject itself also satisfies additional arbitrary requirements.
Installation
npm install fluent-json-validator
Usage / how-to / tutorial
First, of course you need to import the library itself:
import { is } from 'fluent-json-validator'
Then you can create validators like this:
const stringValidator = is.String();
These validators can then validate objects passed to them like this:
stringValidator.validate('some string') // true
stringValidator.validate(42) // false
Of course this whole expression can be written on one single line if you prefer compact solutions:
is.String().validate('some string') // still true
is.String().validate(42) // still false
If some data may not be present, you can use optional validators, which accept missing/undefined data:
const isOptionalNumber = is.optional().Number() // or is.Number().optional()
isOptionalNumber.validate(42) // true
isOptionalNumber.validate() // true
isOptionalNumber.validate('NaN') // false
As for primitive data types, we have validators for strings, numbers and booleans:
const stringValidator = is.String()
const numberValidator = is.Number()
const boolValidator = is.Boolean()
stringValidator.validate('more string') // true
numberValidator.validate(42) // true
boolValidator.validate(true) // true
And for complex/compound data types, i.e. objects, we have the object validator:
const objectValidator = is.Object({
someKey: is.String()
})
This object validator needs a parameter which describes the desired schema of the subject. It must contain the same properties as the subject you want to validate, and it must be made up of other validators.
Using this is very similar to the primitive data type validators:
const someObject = {
someKey: 'this is some string'
}
objectValidator.validate(someObject) // true
const otherObject = {
someKey: 42
}
objectValidator.validate(otherObject) // false
(Of course the above expressions can be written on one line as well. Excercise left for the reader.)
For arrays, you can use the array validator:
const arrayValidator = is.ArrayOf(is.Number())
This one also needs a parameter: a validator, which is going to check all the elements of the array, like this:
arrayValidator.validate([1, 2, 3]) // true
arrayValidator.validate(['some', 'string']) // false
arrayValidator.validate([1, 2, 'impostor']) // false
If you happen to have some data which could have different types, you can validate that as well:
const variableValidator = is.OneOf([is.String(), is.Number()])
This validator needs an array of validators, and the subject will need to match against at least one of them.
variableValidator.validate('some string') // true
variableValidator.validate(42) // also true
variableValidator.validate(true) // false
Lastly, if you need to check for functional requirements, you can do that too:
const ageValidator = is.Number().Which(value => value >= 18)
It needs a lambda/function as a parameter, which will receive the subject to validate, and must return a boolean.
Using it does not require anything special:
ageValidator.validate(18) // true
ageValidator.validate(5) // false
And if you need to validate complex data structures, you can compose big validators from smaller ones, like:
const isPerson = is.Object({
name: is.String(),
gender: is.optional().String(),
age: is.Number()
})
const isLocation = is.Object({
longitude: is.Number(),
latitude: is.Number()
})
// the compound validator.
const isCompany = is.Object({
owner: isPerson,
employees: is.ArrayOf(isPerson),
location: isLocation
})
isCompany.validate({
owner: {
name: 'John Doe',
gender: 'male',
age: 42
},
employees: [
{
name: 'Some Dude',
// note: gender is missing!
age: 18
}, {
name: 'Another Individual',
gender: 'mystery',
age: 99
}
],
location: {
longitude: 42,
latitude: 3.14
}
}) // true
This is actually a very simple example compared to what you can do with this library.
For example you can have an array of values, in which all of them must meet a functional requirement:
const diceRolls = is.ArrayOf(is.Number().Which(value => value > 0 && value < 7))
diceRolls.validate([1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]) // true
diceRolls.validate([1, 6, 3]) // true
diceRolls.validate([9]) // false
diceRolls.validate(['over 9000']) // false
And also you can have a functional requirement which ensures e.g. cross-referencing in complex schemas:
const isFood = is.Object({
name: is.String(),
calories: is.Number()
})
const validator = is.Object({
foods: is.ArrayOf(isFood),
favoriteFoodName: is.String()
}).Which(obj => obj.foods.filter(food => food.name == obj.favoriteFoodName).length == 1)
// ^ == there exists one and only one food which has the name of favoriteFoodName.
console.log(validator.validate({
foods: [
{
name: 'apple',
calories: 52
},
{
name: 'pizza',
calories: 266
}
],
favoriteFoodName: 'apple'
})) // true
In this example above we have a list of foods (with names and calories), and a favorite food, which must exist in the said foods array.
Also don't forget to check the countless tests for inspiration, especially the complex ones at the end!
API docs
In the docs/
folder and also hosted on GitHub Pages. (Don't forget to check the right sidebar of the site!)
Testing
Testcases are listed in tests.js
. Run them with
npm test
Note about code quality (?)
This project most probably does not follow the current Javascript standards and coding style embraced by the global community, thus some people may find the source code weird and/or outright hideous. As I'm not primarily a Javascript developer (and I don't intend to become one) the current implementation is a solution that I could come up with, which works and has the API that I dreamed of.
If you have ideas how to improve the library internals (without breaking the public API) feel free to open an issue or PR to discuss it! I'm open for improvements and constructive criticism.
But why?
Because there is no other JSON object validator with a builder pattern interface like this one. Okay, there is actually Superstruct, but I didn't know about it when I started developing this, and also it is much-much bigger with many features I personally don't need.
So I created this library as a smaller alternative.
License
MIT