Examples

Simple registration form

import { assemble, chain, isRequired, isInteger, isGreaterThanOrEqual } from 'simple-object-validation'

const isValidCustomer = assemble({
  name: isRequired('Name'),
  age: chain([
    isRequired,
    isInteger,
    isGreaterThanOrEqual(18)
  ])('Age'),
})

isValidCustomer({ age: 17 }) // { name: 'Name is required.', age: 'Age must be greater than or equal 18.' }

isValidCustomer({ name: 'Mathew', age: 18 }) // {}

I18n

This is how you can use an internationalization library like i18next in combination with simple-object-validation. Every validator can be called with a configuration object that contains a messageCreator and an optional nameTransformer property. This call will return a new validator that has the same behaviour as the original one, but will return an internationalized error message.

import { isRequired, isGreaterThanOrEqual } from 'simple-object-validation'
import i18next from 'i18next'

const nameTransformer = name => i18next.t(name)

const i18n_required = isRequired({
  messageCreator: (param, name, value) => i18next.t('{{name}} is required.', { name }), nameTransformer
})

const i18n_greaterThanOrEqual = isGreaterThanOrEqual({
  messageCreator: (param, name, value) => i18next.t('{{name}} must be greater than or equal to {{param}}.', { name, param }), nameTransformer
})

export { 
  i18n_required as isRequired,
  i18n_greaterThanOrEqual as isGreaterThanOrEqual
}


import { isRequired, isGreaterThanOrEqual } from './i18n-validation'

isRequired('Zip code')('')            // 'Feld Postleitzahl ist ein Pflichtfeld.'
isGreaterThanOrEqual(18)('Age')('17') // 'Feld Alter muss größer oder gleich 18 sein.'

Validators

isRequired

Checks if a value is set. That means if is not null, not undefined and not an empty String.

import { isRequired } from 'simple-object-validation'

isRequired('Name')(null)      // 'Name is required.'
isRequired('Name')(undefined) // 'Name is required.'
isRequired('Name')('')        // 'Name is required.'
isRequired('Name')(false)     // undefined
isRequired('Name')(42)        // undefined
isRequired('Name')('Mathew')  // undefined

isRequiredIf

Checks if a value is set (like isRequired) but depending on a specified condition.

import { isRequiredIf } from 'simple-object-validation'

const cityIsNotEmpty = values => typeof values.city !== 'undefined' && values.city !== null && values.city !== ''

const isValidLocation = assemble({
  country: isRequiredIf(cityIsNotEmpty)('Country')
})

isValidLocation({
  city: 'Berlin'
})
// { country: 'Country is required.' }

isValidLocation({
  city: 'Berlin',
  country: 'Germany'
})
// {}

isValidLocation({})
// {}

isAlphanumeric

Determines wether the given value is alphanumeric.

import { isAlphanumeric } from 'simple-object-validation'

isAlphanumeric('Code')('...')      // 'Code must be alphanumeric.'
isAlphanumeric('Code')('%$!')      // 'Code must be alphanumeric.'
isAlphanumeric('Code')(' ')        // 'Code must be alphanumeric.'
isAlphanumeric('Code')('a1B2c3D4') // undefined

isNumeric

Determines wether the given value is numeric. Strings are allowed.

import { isNumeric } from 'simple-object-validation'

isNumeric('Number')('abc')  // 'Number must be numeric.'
isNumeric('Number')('%$!')  // 'Number must be numeric.'
isNumeric('Number')(' ')    // 'Number must be numeric.'
isNumeric('Number')(42)     // undefined
isNumeric('Number')('42.0') // undefined

isInteger

Determines wether the given value is an integer. Strings are allowed.

import { isInteger } from 'simple-object-validation'

isInteger('Number')('abc')  // 'Number must be an integer.'
isInteger('Number')('%$!')  // 'Number must be an integer.'
isInteger('Number')('1.')   // 'Number must be an integer.'
isInteger('Number')('42.0') // 'Number must be an integer.'
isInteger('Number')(42.0)   // undefined
isInteger('Number')(42)     // undefined
isInteger('Number')('42')   // undefined

isBetween

Checks if a given value is between a minimum and a maximum value.

import { isBetween } from 'simple-object-validation'

isBetween({ min: 0, max: 100 })('Percentage')('-1')  // 'Percentage must be between 0 and 100.'
isBetween({ min: 0, max: 100 })('Percentage')(101)   // 'Percentage must be between 0 and 100.'
isBetween({ min: 0, max: 100 })('Percentage')(100.1) // 'Percentage must be between 0 and 100.'
isBetween({ min: 0, max: 100 })('Percentage')(42)    // undefined
isBetween({ min: 0, max: 100 })('Percentage')('1')   // undefined

isGreaterThanOrEqual

Checks if a given value is greater than or equal to another value.

import { isGreaterThanOrEqual } from 'simple-object-validation'

isGreaterThanOrEqual(1)('Number')('-1') // 'Number must be greater than or equal to 1.'
isGreaterThanOrEqual(1)('Number')(0)    // 'Number must be greater than 1.'
isGreaterThanOrEqual(1)('Number')(1)    // undefined
isGreaterThanOrEqual(1)('Number')(123)  // undefined

isLessThanOrEqual

Checks if a given value is less than or equal to another value.

import { isLessThanOrEqual } from 'simple-object-validation'

isLessThanOrEqual(2048)('Number')('2500')  // 'Number must be less than or equal to 2048.'
isLessThanOrEqual(2048)('Number')(1000000) // 'Number must be less than or equal to 2048.'
isLessThanOrEqual(2048)('Number')(1)       // undefined
isLessThanOrEqual(2048)('Number')(123)     // undefined

isEqualTo

Checks if a value is equal to another value.

import { isEqualTo } from 'simple-object-validation'
import { isValidPassword } from './password-validation' // Custom password validator

// Configuration by property:
const isEqualToPassword = isEqualTo({
  messageCreator: () => 'The passwords must be equal.'  // Overriding default message
})({ property: 'password' })                            // password is the property that must be equal

const isValidUser = assemble({
  login: isRequired('Login'),
  password: isValidPassword('Password'),
  repeatPassword: isEqualToPassword('Repeat password')
})

isValidUser({
  login: 'Administrator',
  password: '9MMGIVgt',
  repeatPassword: '9MMGIVgt',
})
// {}

isValidUser({
  login: 'Administrator',
  password: '9MMGIVgt',
  repeatPassword: '9MMGIVgz',
})
// { repeatPassword: 'The passwords must be equal.' }


// Configuration by value resolver:
const isValidFoo = assemble({
  foo: assemble({
    baz: isRequired('Baz')
  }),
  bar: isEqualTo({
    value: values => values.foo && values.foo.baz,
    name: 'Baz'
  })('Bar')
})

isValidFoo({
  foo: {
    baz: '123'
  },
  bar: '123'
})
// {}

isValidFoo({
  foo: {
    baz: '123'
  },
  bar: '1234'
})
// { bar: 'Bar must be equal to Baz.' }

Bring your own validator

You can easily build your own validators using the built-in validator factory function.

import { validator } from 'simple-object-validation'

// without parameter:
const containsLetterA = validator(
  (value, param, allValues) => value && value.toLowerCase().indexOf('a') > -1,
  (param, name, value) => `${name} does not contain letter A.`
)

// with parameter:
const containsLetter = validator(
  (value, param, allValues) => value && value.toLowerCase().indexOf(param.toLowerCase()) > -1,
  {
    expectParameter: true, // this option is only mandatory when the parameter is a string (see validator for more information)
    messageCreator: (param, name, value) => `${name} does not contain letter ${param.toUpperCase()}.`
  }
)

containsLetterA('Field X')('foo')     // 'Field X does not contain letter A.'
containsLetter('y')('Field X')('bar') // 'Field X does not contain letter Y.'

See validator for more information.

Or you can just use a pure javascript function and plug it in:

import { assemble, isRequired } from 'simple-object-validation'
import { isCool } from './custom-validation'

const validate = assemble({
  property1: isRequired('Property 1'),
  property2: value => {
    if (!value) {
      return 'You should really enter something cool here.'
    } else if (!isCool(value)) {
      return 'Oh, come on! That is not a cool value!'
    }
    return undefined
  },
})

validate({})                                                          // { property1: 'Property 1 is required.', property2: 'You should really enter something cool here.' }
validate({ property1: 'bar' })                                        // { property2: 'You should really enter something cool here.' }
validate({ property1: 'bar', property2: 'foo' })                      // { property2: 'Oh, come on! That is not a cool value!' }
validate({ property1: 'bar', property2: 'simple-object-validation' }) // {}

Core API

assemble

This function is used to combine validators in order to validate whole objects. Your input is an object with some values. Your output is an object with some error messages. You can also validate nested objects. The error messages will then be nested as well. Every assembled validator will be called with its particular value and a second parameter (allValues) which points to the whole object.

import { assemble, isRequired, isInteger } from 'simple-object-validation'

const isValidUser = assemble({
  name: isRequired('Full name'),
  age: isInteger('Age'),
  address: assemble({
    addressLine1: isRequired('Address Line 1'),
    zipCode: isRequired('Zip code'),
    city: isRequired('City'),
  }),
})

isValidUser({ age: 17 }) // { name: 'Full name is required.', address: { addressLine1: 'Address Line 1 is required.', zipCode: 'Zip code is required.', city: 'City is required.' } }
isValidUser({ name: 'Angela Merkel', age: 63, address: { addressLine1: 'Willy-Brandt-Straße 1', zipCode: '10557', city: 'Berlin' } }) // undefined

Options:

Option Type Default Description
ignoreIfMissing boolean false If ignoreIfMissing is set to true, no validation will take place for the object. Normally a missing object, that is defined by an assemble function, is treated like an existing object with every property set to undefined.
strictValidation boolean false If strictValidation is set to true, every property must be defined for validation. If a property is not defined, validation will throw an error. However if a property can not be validated, it should then be part of a whitelist (see whitelist option). This option is security-relevant and it is recommended when running validation on the server.
whitelist string[] [] This option is only necessary when strictValidation is set to true. The whitelist contains any property name that can be ignored for validation.

Examples:

const isValid = assemble({
  foo: isFoo('Foo'),
  bar: isBar('Bar'),
}, {
  strictValidation: true,
  whitelist: ['baz'],
})

isValid({ foo: 'foo', bar: 'bar', baz: { a: ['b', 'c'] } }) // {}
isValid({ foo: 'foo', bar: 'bar', query: 'SELECT login, password FROM user' }) // throws Error

chain

If there are several validators that need to be applied to a value, you can use the chain function.

import { chain, isRequired, isInteger, isGreaterThanOrEqual } from 'simple-object-validation'

const isValidAge = chain([
  isRequired,
  isInteger,
  isGreaterThanOrEqual(18)
])

isValidAge('Age')()    // 'Age is required.'
isValidAge('Age')('a') // 'Age must be an integer.'
isValidAge('Age')(17)  // 'Age must be greater than or equal to 18.'
isValidAge('Age')(18)  // undefined

each

If you want to validate an array of values you can use each.

import { each, isAlphanumeric } from 'simple-object-validation'

const isValidCodeArray = each(i => isAlphanumeric(`Code number ${i + 1}`))

isValidCodeArray(['123456Seven', '%!()', '42']) // [undefined, 'Code number 2 must be alphanumeric.', undefined]
isValidCodeArray(['123456Seven', 'abc', '42'])  // [undefined, undefined, undefined]

match

If you want to map each value of an array to a specific validator, you can use match.

import { match, isRequired, isNumeric, isGreaterThanOrEqual, isLessThanOrEqual } from 'simple-object-validation'

const isValidTuple = match([
  isRequired('Value 1'),
  isNumeric('Value 2'),
  isGreaterThanOrEqual(0)('Value 3'),
  isLessThanOrEqual(10)('Value 4'),
])

isValidTuple([undefined, '%!()', '0', '42']) // ['Value 1 is required.', 'Value 2 must be numeric.', undefined, 'Value 4 must be less than or equal to 10.']
isValidTuple(['foo', '1', '1', '1'])         // [undefined, undefined, undefined, undefined]

validator

You can use this function to create new validators. These validators can then be easily combined with assemble, chain, each and match.

Definition

(checkFunction: (value: any, param: any) => boolean, messageCreator: (param: any, name: string, value: any) => string)
=> (param: any) => (name: string) => (value: any, allValues: any) => undefined | string

Okay, okay! This looks difficult at first sight, definitely. But it totally makes sense. This is how it works:

Let's start with line two of the definition which describes the validator that is created using validator(). Generally in simple-object-validation every validator first takes a configuration parameter to further specify its behaviour. This is going to return another function that waits for being called with the field name. That call however will return a function that finally wants to be called with a value (and possibly the other values [allValues] in the context of the validation, see assemble). It will either return undefined or an error message.

BUT: You don't have to care about that at all. You only have to provide two functions (as you can see in line one). The first function (checkFunction) basically provides the validation logic while the second function (messageCreator) creates an error message based on the variables that are passed to the validator during the configuration process.

Just look at this example:

import { validator } from 'simple-object-validation'

const hasMinLength = validator(
  (value, param, allValues) => value.length >= param,
  (param, name, value) => `${name} must have a minimum length of ${param}!`
)

hasMinLength(4)('My string')('foo') // 'My string must have a minimum length of 4!'
hasMinLength(4)('My string')('a longer string') // undefined

Validator without configuration parameter

But a configuration does not always make sense. In some cases you could just leave it away because it's optional or maybe you don't need it at all (e. g. isRequired). So if you call the validator directly with a field name instead this will be detected by the validator and it is going to behave like if it had been parameterized (with undefined!) before. You can see that in the following example:

import { validator } from 'simple-object-validation'

const isMeaningOfLife = validator(
  (value, param, allValues) => value === 42,
  (param, name, value) => `${name} must be the meaning of life!`
)

isMeaningOfLife('Magic Number')(3.14) // 'Magic Number must be the meaning of life'
isMeaningOfLife('Magic Number')(42) // undefined

As you can see, no configuration parameter was passed to the validator. Instead it was called directly with the field name. Like this you can have validators with parameters and ones without next to each other like in this example.

Important note about strings as configuration parameters

The validator detects a deliberately missing configuration parameter by testing for a string. So in this situation a string is always interpreted as the field name. If you want to use a string as a parameter you'll have to configure the validator like this:

import { validator } from 'simple-object-validation'
  
const isEqualToString = validator(
  (value, param, allValues) => value === param,
  {
    expectParameter: true,
    messageCreator: (param, name, value) => `${name} must be equal to ${param}!`
  }
)

isEqualToString('foo')('Foobar')('bar') // 'Foobar must be equal to foo'
isEqualToString('foo')('Foobar')('foo') // undefined

So there is one little disadvantage here: If you want to use strings as parameters you cannot make them optional.