A list common TypeScript validator-functions and some useful utilities to go with them.
A simple, but long, list of validator-functions commonly used when checking values in TypeScript. This is not meant to replace advanced schema validation libraries like zod
, valibot
, jet-schema
etc. This is just a list of pre-defined validator-functions to save you time and boilerplate code in TypeScript.
import { isOptionalString, isRecord } from 'jet-validators';
if (isOptionalString(val)) {
// val is string | undefined
}
if (isRecord(val)) {
val['foo'] = 'bar';
}
- Contains validator-functions for the vast majority of real world scenarios you will encounter.
- For basic validators, there's no initialization step, you just import the validator-function and start using it.
- Overload regular expressions using environment variables.
- Contains some useful utilities for modifying values before validation.
- Also has some utilities for simple object schema validation.
- Zero dependency!
npm i -s jet-validators
These can be imported and used directly and don't require any configuration.
- isUndef
- isNull
- isNullish (is
null
orundefined
)
- isBoolean
- isOptionalBoolean
- isNullableBoolean
- isNullishBoolean
- isBooleanArray
- isOptionalBooleanArray
- isNullableBooleanArray
- isNullishBooleanArray
Is it a valid boolean after calling the parseBoolean
utility function.
- isValidBoolean
- isOptionalValidBoolean
- isNullableValidBoolean
- isNullishValidBoolean
- isValidBooleanArray
- isOptionalValidBooleanArray
- isNullableValidBooleanArray
- isNullishValidBooleanArray
- isNumber
- isOptionalNumber
- isNullableNumber
- isNullishNumber
- isNumberArray
- isOptionalNumberArray
- isNullableNumberArray
- isNullishNumberArray
- isBigInt
- isOptionalBigInt
- isNullableBigInt
- isNullishBigInt
- isBigIntArray
- isOptionalBigIntArray
- isNullableBigIntArray
- isNullishBigIntArr
- isValidNumber
- isOptionalValidNumber
- isNullableValidNumber
- isNullishValidNumber
- isValidNumberArray
- isOptionalValidNumberArray
- isNullableValidNumberArray
- isNishValidNumArr
- isString
- isOptionalString
- isNullableString
- isNullishString
- isStringArray
- isOptionalStringArray
- isNullableStringArray
- isNullishStringArray
- isNonEmptyString
- isOptionalNonEmptyString
- isNullableNonEmptyString
- isNullishNonEmptyString
- isNonEmptyStringArray
- isOptionalNonEmptyStringArray
- isNullableNonEmptyStringArray
- isNullishNonEmptyStringArray
- TNonEmptyStr
- isSymbol
- isOptionalSymbol
- isNullableSymbol
- isNullishSymbol
- isSymbolArray
- isOptionalSymbolArray
- isNullableSymbolArray
- isNullishSymbolArray
Is argument an instance of Date
with a valid time value: "!isNaN(arg.getTime())" => true
- isDate
- isOptionalDate
- isNullableDate
- isNullishDate
- isDateArray
- isOptionalDateArray
- isNullableDateArray
- isNullishDateArray
Is argument a valid date after wrapping with new Date()
(could be Date
, string
, number
)
- isValidDate
- isOptionalValidDate
- isNullableValidDate
- isNullishValidDate
- isValidDateArray
- isOptionalValidDateArray
- isNullableValidDateArray
- isNullishValidDateArray
- isObject
- isOptionalObject
- isNullableObject
- isNullishObject
- isObjectArray
- isOptionalObjectArray
- isNullableObjectArray
- isNullishObjectArray
Checks if the argument is a non-null non-array object. Type predicate is Record<string, unknown>
.
- isRecord
- isOptionalRecord
- isNullableRecord
- isNullishRecord
- isRecordArray
- isOptionalRecordArray
- isNullableRecordArray
- isNullishRecordArray
- TRecord (type)
- isFunction
- isOptionalFunction
- isNullableFunction
- isNullishFunction
- isFunctionArray
- isOptionalFunctionArray
- isNullableFunctionArray
- isNullishFunctionArray
Verifies the argument matches the regular-expression. Note than an empty string will validate to true
for each function.
The regular expressions for each function below can be overwritten using the environment variables. To overload a regular expression create an environment variables with the format:
- JET_VALIDATORS_REGEX_{name of the function in uppercase} (i.e.
JET_VALIDATORS_REGEX_EMAIL=^\S+@\S+\.\S+$
)
- isColor
- isOptionalColor
- isNullableColor
- isNullishColor
- TColor (type)
- isEmail
- isOptionalEmail
- isNullableEmail
- isNullishEmail
- TEmail (type)
- isUrl
- isOptionalUrl
- isNullableUrl
- isNullishUrl
- TURL (type)
- isAlphaNumericString
- isOptionalAlphaNumericString
- isNullableAlphaNumericString
- isNullishAlphaNumericString
- TAlphabeticStr (type)
- isAlphabeticString
- isOptionalAlphabeticString
- isNullableAlphabeticString
- isNullishAlphabeticString
- TAlphaNumericStr (type)
These require an initialization step which will return a validator function.
- isInArray
- isOptionalInArray
- isNullableInArray
- isNullishInArray
Does the argument strictly equal any item in the array:
const isInArrTest = isInArray(['1', '2', '3']);
isInArrTest('1'); // => true
- isInRange
- isOptionalInRange
- isNullableInRange
- isNullishInRange
- isInRangeArray
- isOptionalInRangeArray
- isNullableInRangeArray
- isNullishInRangeArray
Will check if the argument (can be a number-string
or a number
) is in the provided range. The function will check if the argument is greater-than the first param and less-than the second param. If you wish to include the min or max value in the range (i.e. greater-than-or-equal-to) wrap it in square brackets. If you wish to leave off a min or max pass an empty array []
. If you want to check if the number is not between two numbers, use the bigger number for the first param and the lesser number for the second:
// Between 0 and 100
const isBetween0And100 = isInRange(0, 100);
isBetween0And100(50); // false
isBetween0And100('100'); // false
isBetween0And100(0); // true
// Is negative
const isNegative = isInRange([], 0);
isNegative(0); // false
isNegative(-.0001); // true
const isOptPositive = isOptionalInRange(0, []);
isOptPositive(undefined); // true
isOptPositive(1_000_000_000); // true
// 0 to 100
const isFrom0to100 = isInRange([0], [100]);
isFrom0to100('50'); // true
isFrom0to100(100); // true
isFrom0to100(0); // true
// less than 50 or greater than 100
const lessThan50OrGreaterThan100 = isInRange(100, 50);
lessThan50OrGreaterThan100(75); // false
lessThan50OrGreaterThan100(101); // true
- isKeyOf
- isOptionalKeyOf
- isNullableKeyOf
- isNullishKeyOf
- isKeyOfArray
- isOptionalKeyOfArray
- isNullableKeyOfArray
- isNullishKeyOfArray
Checks if the argument is a key of the object. Note that this will not work for symbols:
const someObject = {
foo: 'bar',
bada: 'bing',
} as const;
const isKeyofSomeObject = isKeyOf(someObject);
isKeyofSomeObject('foo'); // true
const isKeyofSomeObjectArr = isNullableKeyOfArray(someObject);
isKeyofSomeObjectArr(['bada', 'foo']); // true
- isEnum
- isOptionalEnum
- isNullableEnum
- isNullishEnum
- TEnum (type)
Check if the argument is a valid enum object. Unlike other complex validators, this does not require an inialization step. Note this will not work for mixed enum types: see: eslint@typescript-eslint/no-mixed-enums
:
enum StringEnum {
Foo = 'foo',
Bar = 'bar',
}
isEnum(StringEnum) // true
- isEnumVal
- isOptionalEnumVal
- isNullableEnumVal
- isNullishEnumVal
- isEnumValArray
- isOptionalEnumValArray
- isNullableEnumValArray
- isNullishEnumValArray
Check if the argument is a value of the enum. You must initialize this with a valid non-mixed enum type: see: eslint@typescript-eslint/no-mixed-enums
:
enum NumberEnum {
Foo,
Bar,
}
const isNumberEnumVal = isEnumVal(NumberEnum);
isNumberEnumVal(NumberEnum.Foo); // true
These complement the validator-functions and are useful if you need to modify a value before checking it or validate an object's schema. Utilities need to be imported using /utils
at the end of the library name:
import { parseObject } from 'jet-validators/utils';
Remove null
/undefined
from type-predicates and runtime validation:
const isString = nonNullable(isNullishString);
isString(null); // false
isString(undefined); // false
Accepts a transformation function for the first argument, a validator for the second, and returns a validator-function which calls the transform function before validating. The returned validator-function provides a callback as the second argument, if you need to access the transformed value. You should use transform
if you need to modify a value when using parseObject
or testObject
:
const isNumArrWithParse = transform((arg: string) => JSON.parse(arg), isNumberArray);
isNumArrWithParse('[1,2,3]', val => {
isNumberArray(val); // true
})); // true
- parseBoolean
- parseOptionalBoolean
- parseNullableBoolean
- parseNullishBoolean
Converts the following values to a boolean. Note will also covert the string equivalents:
"true" or true
:true
(case insensitive i.e."TrUe" => true
)"false" or false
:false
(case insensitive i.e."FaLsE" => false
)"1" or 1
:true
"0" or 0
:false
parseBoolean('tRuE'); // true
parseBoolean(1) // true
parseBoolean('0') // false
- parseJson
- parseOptionalJson
- parseNullableJson
- parseNullishJson
Calls the JSON.parse
function, if the argument is not a string an error will be thrown:
const numberArr = parseJson<number[]>('[1,2,3]');
isNumberArray(val); // true
If you need to validate an object schema, you can pass a validator object with the key being a property of the object and the value being any of the validator-functions in this library OR you can write your own validator-function (see the Custom Validators section).
These functions aren't meant to replace full-fledged schema validation libraries (like zod, ajv, etc), they're just meant as simple object validating tools where using a separate schema validation library might be overkill.
default
Extra properties will be purged but not raise errors.- parseObject
- parseOptionalObject
- parseNullableObject
- parseNullishObject
- parseObjectArray
- parseOptionalObjectArray
- parseNullableObjectArray
- parseNullishObjectArray
loose
Extra properties will not be purged or raise errors.- looseParseObject
- looseParseOptionalObject
- looseParseNullableObject
- looseParseNullishObject
- looseParseObjectArray
- looseParseOptionalObjectArray
- looseParseNullableObjectArray
- looseParseNullishObjectArray
strict
Extra properties will be purged and raise errors.- strictParseObject
- strictParseOptionalObject
- strictParseNullableObject
- strictParseNullishObject
- strictParseObjectArray
- strictParseOptionalObjectArray
- strictParseNullableObjectArray
- strictParseNullishObjectArray
This function iterates an object (and any nested objects) and runs the validator-functions against each property. If every validator-function passed, the argument will be returned while purging any properties not in the schema. If it does not pass, then the function returns false
. You can optionally pass an error-handler function as the second argument which will fire whenever a validator-function fails.
The format for the onError
callback function is as follows. If the validator-function throws an error, it will be passed to the caughtErr
param (see below snippet):
(errorArray: IParseObjectError[]) => void;
// The IParseObjectError
{
info: string;
prop?: string;
value?: unknown;
caught?: string;
index?: number;
children?: IParseObjectError[];
}
Example of using parseObject
with a custom error callback:
import { parseObject, IParseObjectError } from 'jet-validators/utils';
interface IUser {
id: number;
name: string;
address: {
city: string;
zip: number;
}
}
const parseUser = parseObject<'pass "IUser" here if you want to enforce schema props'>({
id: transform(Number, isNumber),
name: isString,
address: {
city: isString,
zip: isNumber,
}
}, (errorArray: IParseObjectError[]) => {
console.error(JSON.stringify(errorArray, null, 2));
});
const user: IUser = parseUser({
id: '5',
name: 'john',
email: '--',
address: {
city: 'seattle',
zip: 99999,
}
});
// 'user' variable above:
// {
// id: 5,
// name: 'john',
// address: {
// city: 'seattle',
// zip: 99999,
// }
// }
- If you use the
parseObjectArray
the error callback handler will also pass the index of the object calling the error function:
const parseUserArrWithError = parseObjectArray({
id: isNumber,
name: isString,
}, errors => {
console.log(errors) // =>
// [
// {
// info: 'Validator-function returned false.',
// prop: 'id',
// value: '3',
// index: 2
// }
// ]
});
parseUserArrWithError([
{ id: 1, name: '1' },
{ id: 2, name: '2' },
{ id: '3', name: '3' },
]);
default
Extra properties will be purged but not raise errors.- testObject
- testOptionalObject
- testNullableObject
- testNullishObject
- testObjectArray
- testOptionalObjectArray
- testNullableObjectArray
- testNullishObjectArray
loose
Extra properties will not be purged or raise errors.- looseTestObject
- looseTestOptionalObject
- looseTestNullableObject
- looseTestNullishObject
- looseTestObjectArray
- looseTestOptionalObjectArray
- looseTestNullableObjectArray
- looseTestNullishObjectArray,
strict
Extra properties will be purged and raise errors.- strictTestObject
- strictTestOptionalObject
- strictTestNullableObject
- strictTestNullishObject
- strictTestObjectArray
- strictTestOptionalObjectArray
- strictTestNullableObjectArray
- strictTestNullishObjectArray
testObject
is nearly identical to parseObject
(it actually calls parseObject
under-the-hood) but returns a type-predicate instead of the argument passed. Transformed values and purging non-schema keys will still happen as well:
const user: IUser = {
id: '5',
name: 'john',
email: '--',
address: {
city: 'seattle',
zip: 99999,
}
}
const testUser = testObject(user);
if (testUser(user)) { // user is IUser
// 'user' variable above:
// {
// id: 5,
// name: 'john',
// address: {
// city: 'seattle',
// zip: '99999',
// }
// }
}
If you have nested objects on another parent object that you want to test or parse, you can add nested testObject
functions to the parent one. Note that parseObject
requires validator-functions in the schema (they must return a type-predicate), so you can only use testObject
for nested schemas.
IMPORTANT: Due to the limitations with Typescript, if you do not add a generic to a nested schema, all required properties must still be there for typesafety; however, typesafety will not protect the nested schema from extra properties (see below):
import { parseObject, testObject } from 'jet-validators/utils';
interface IUser {
id: number;
name: string;
address: {
city: string;
zip: number;
}
}
const parseUser = parseObject<IUser>({
id: isNumber,
name: isString,
address: {
city: isString,
zip: isNumber,
// foo: isString, // Causes type error
},
});
const parseUser1 = parseObject<IUser>({
id: isNumber,
name: isString,
address: testObject({
city: isString,
zip: isNumber,
foo: isString, // DOES NOT cause type error because of typescript limitations
}),
});
const parseUser3 = parseObject<IUser>({
id: isNumber,
name: isString,
address: testObject<IUser['address']>({
city: isString,
zip: isNumber,
// foo: isString, // Causes type error
}),
});
- If you want to use an external nested validator-function for whatever reason, make sure you pass down the
arg
param AND theoptions
/errorCb
params so that any nested parse functions can receive the options/errors from the parent.
In this example we needed to parse an address object. So we don't have to initialize the address schema twice we just wrap the parseAddr
function and convert it to a test function (make it return a type-predicate):
import { parseObject, testNullishObject, TParseOnError, IParseOptions } from 'jet-validators/utils';
interface IAddress {
city: string;
zip: number;
}
const parseAddr = parseNullishObject({
city: isString,
zip: isNumber,
});
function newAddress(arg: unknown): IAddress {
return parseAddr(arg);
}
const parseUser = parseObject({
id: isNumber,
name: isString,
address: (
arg: unknown,
optionsOrErrCb?: IParseOptions | TParseOnError,
errorCb?: TParseOnError,
): arg is IAddress => !!parseAddr(arg, optionsOrErrCb, errorCb),
});
For parseObject
and testObject
you aren't restricted to the validator-functions in jet-validators
. You can write your own validator-function, just make sure your argument is unknown
and it returns a type predicate:
type TRelationalKey = number;
interface IUser {
id: TRelationalKey;
name: string;
}
// The custom validator-function
const isRelationalKey = (arg: unknown): arg is TRelationalKey => {
return isNumber(arg) && arg >= -1;
}
const parseUser = parseObject({
id: isRelationalKey,
name: isString,
});
const user: IUser = parseUser({
id: 5,
name: 'joe',
});
If you want to wrap the parseObject
or testObject
functions cause for whatever reason, you need to import the TSchema
type and have your generic extend it:
import { isNumber, isString } from 'jet-validators';
import { parseObject, TSchema } from 'jet-validators/utils';
interface IUser {
id: number;
name: string;
}
// Wrap without generic
const customParse = (schema: TSchema) => {
return parseObject(schema, errors => throw new YourCustomErrorObject(errors))
}
// Wrap with generic
const customParse2 = <T>(schema: TSchema<T>) => {
return parseObject<T>(schema, errors => throw new YourCustomErrorObject(errors))
}
const parseUser = customParse({
id: isNumber,
name: isString,
});
const user: IUser = parseUser('...whatever...');
const parseUser2 = customParse2<IUser>({
id: isNumber,
name: isString,
// address: isString, // Will cause type error
});
For parseObject/testObject
you can control what happens if extra properties are found in the argument object. This is done by important functions suffixed with loose
or strict
. The default parseObject/testObject
functions are configured with the default
safety setting:
loose
: Extra properties will not be purged or raise errors.default
: Extra properties will be purged but not raise errors.strict
: Extra properties will be purged and raise errors.
Example of the options
argument. If you need to pass an error callback too pass it as the third argument. Note that the highest level passed options
object will overwrite any nested one:
import { isNumber, isString } from 'jet-validators';
import { strictParseObject } from 'jet-validators/utils';
const testUser = strictParseObject({
id: isString,
name: isNumber,
}, errors => console.log(errors));
testUser({ id: 1, name: 'a', city: 'seattle' }); // This will raise errors
If you're using nested parse/test functions make sure you use the right function on the nested object if you want it to match the parent's safety level:
import { isNumber, isString } from 'jet-validators';
import { parseObject, strictParseObject } from 'jet-validators/utils';
const testUser = strictParseObject({
id: isString,
name: isNumber,
address: { // Will still do 'strict' parsing
city: isString,
zip: isNumber,
},
country: parseObject({ // Will do 'default' parsing
name: isString,
code: isNumber
}),
});
Performs a deep comparison of two objects. I know there's a ton of object comparison options out there (i.e. lodash.isEqual
) but this along with customDeepCompare
is a little more versatile. If two values are both instances of Date
then their epoch (.getTime()
) will be used for comparison. This can be overriden with the customDeepCompare
function.
If you wish to access the values everytime a comparison fails or modify the behavior of the deepCompare
function, you can use the customDeepCompare
which receives a callback function and/or an options object. The value returned will be a custom deepCompare
function with the parameters saved.
customDeepCompare("callback or options")
orcustomDeepCompare("options", "callback")
disregardDateException?: boolean;
onlyCompareProps?: string | string[];
convertToDateProps?: string | string[] | { rec: boolean, props: string | string [] };
disregardDateException
: By default, date objects are compared using the epoch time value from.getTime()
not the key value pairs on the object itself. If you wish to disregard this, setdisregardDateException: true
.onlyCompareProps
: If you want to compare some properties in two objects and not the full object, you can pass a string or an array of strings and only those keys will be used in the comparison. Note that this will not affect arrays, so that if you compare an array of objects the options will be passed down to those objects. This option is not recursive so will not affect any nested objects.convertToDateProps
: If you want a property or properties to be converted to a date object before comparison, you can pass a string or an array of strings and those properties with be converted toDate
objects. By default this option IS recursive. If you wish to make it not recursive you can pass an object instead of an array or string array withrec: false
. I find this option especially helpful if work a lot with IO objects wereDates
often get stringified.
import { deepCompare, customDeepCompare } from 'jet-validators/util';
const deepCompareAlt = customDeepCompare({
convertToDateProps: 'created',
onlyCompareProps: ['id', 'created'],
});
const date1 = new Date('2012-6-17'),
user1 = { id: 1, created: date1 },
user2 = { id: 1, created: date1.getTime() },
user3 = { id: 1, name: 'joe', created: date1 },
deepCompare(user1, user2); // => false
deepCompare(user1, user3); // => false
deepCompareAlt(user1, user2); // => true
deepCompareAlt(user1, user3); // => true
(key: string, val1: unknown, val2: unknown) => void;
- The callback function provides the values that failed during the comparison and will fire everytime a comparison fails:
import { customDeepCompare } from 'jet-validators/util';
const deepCompare = customDeepCompare((key, val1, val2) => console.log(key, val1, val2));
// This will return false and print out "id,1,2" and "name,joe,jane"
deepCompare({ id: 1, name: 'joe' }, { id: 2, name: 'jane' }); // => false