Skip to content

A list common typescript validator-functions and some useful utilities to go with them.

License

Notifications You must be signed in to change notification settings

seanpmaxwell/jet-validators

Folders and files

NameName
Last commit message
Last commit date

Latest commit

3f72876 · Mar 21, 2025
Mar 16, 2025
Mar 16, 2025
Mar 21, 2025
Dec 8, 2024
Dec 9, 2024
Dec 6, 2024
Dec 9, 2024
Mar 16, 2025
Dec 6, 2024
Dec 7, 2024
Mar 21, 2025
Dec 9, 2024
Dec 9, 2024

Repository files navigation

jet-validators 🧑✈️

A list common TypeScript validator-functions and some useful utilities to go with them.


Table of Contents


Introduction

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.

Quick Glance

import { isOptionalString, isRecord } from 'jet-validators';

if (isOptionalString(val)) {
  // val is string | undefined
}

if (isRecord(val)) {
  val['foo'] = 'bar';
}

Why jet-validators

  • 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!

Installation

npm i -s jet-validators

Basic Validators

These can be imported and used directly and don't require any configuration.

Nullables

  • isUndef
  • isNull
  • isNullish (is null or undefined)

isBoolean

  • isBoolean
  • isOptionalBoolean
  • isNullableBoolean
  • isNullishBoolean
  • isBooleanArray
  • isOptionalBooleanArray
  • isNullableBooleanArray
  • isNullishBooleanArray

isValidBoolean

Is it a valid boolean after calling the parseBoolean utility function.

  • isValidBoolean
  • isOptionalValidBoolean
  • isNullableValidBoolean
  • isNullishValidBoolean
  • isValidBooleanArray
  • isOptionalValidBooleanArray
  • isNullableValidBooleanArray
  • isNullishValidBooleanArray

isNumber

  • isNumber
  • isOptionalNumber
  • isNullableNumber
  • isNullishNumber
  • isNumberArray
  • isOptionalNumberArray
  • isNullableNumberArray
  • isNullishNumberArray

isBigInt

  • isBigInt
  • isOptionalBigInt
  • isNullableBigInt
  • isNullishBigInt
  • isBigIntArray
  • isOptionalBigIntArray
  • isNullableBigIntArray
  • isNullishBigIntArr

isValidNumber

  • isValidNumber
  • isOptionalValidNumber
  • isNullableValidNumber
  • isNullishValidNumber
  • isValidNumberArray
  • isOptionalValidNumberArray
  • isNullableValidNumberArray
  • isNishValidNumArr

isString

  • isString
  • isOptionalString
  • isNullableString
  • isNullishString
  • isStringArray
  • isOptionalStringArray
  • isNullableStringArray
  • isNullishStringArray

isNonEmptyString

  • isNonEmptyString
  • isOptionalNonEmptyString
  • isNullableNonEmptyString
  • isNullishNonEmptyString
  • isNonEmptyStringArray
  • isOptionalNonEmptyStringArray
  • isNullableNonEmptyStringArray
  • isNullishNonEmptyStringArray
  • TNonEmptyStr

isSymbol

  • isSymbol
  • isOptionalSymbol
  • isNullableSymbol
  • isNullishSymbol
  • isSymbolArray
  • isOptionalSymbolArray
  • isNullableSymbolArray
  • isNullishSymbolArray

isDate

Is argument an instance of Date with a valid time value: "!isNaN(arg.getTime())" => true

  • isDate
  • isOptionalDate
  • isNullableDate
  • isNullishDate
  • isDateArray
  • isOptionalDateArray
  • isNullableDateArray
  • isNullishDateArray

isValidDate

Is argument a valid date after wrapping with new Date() (could be Date, string, number)

  • isValidDate
  • isOptionalValidDate
  • isNullableValidDate
  • isNullishValidDate
  • isValidDateArray
  • isOptionalValidDateArray
  • isNullableValidDateArray
  • isNullishValidDateArray

isObject

  • isObject
  • isOptionalObject
  • isNullableObject
  • isNullishObject
  • isObjectArray
  • isOptionalObjectArray
  • isNullableObjectArray
  • isNullishObjectArray

isRecord

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

  • isFunction
  • isOptionalFunction
  • isNullableFunction
  • isNullishFunction
  • isFunctionArray
  • isOptionalFunctionArray
  • isNullableFunctionArray
  • isNullishFunctionArray

Regular Expressions

Verifies the argument matches the regular-expression. Note than an empty string will validate to true for each function.

Overloading with environment variables

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

  • isColor
  • isOptionalColor
  • isNullableColor
  • isNullishColor
  • TColor (type)

isEmail

  • isEmail
  • isOptionalEmail
  • isNullableEmail
  • isNullishEmail
  • TEmail (type)

isUrl

  • isUrl
  • isOptionalUrl
  • isNullableUrl
  • isNullishUrl
  • TURL (type)

isAlphaNumericString

  • isAlphaNumericString
  • isOptionalAlphaNumericString
  • isNullableAlphaNumericString
  • isNullishAlphaNumericString
  • TAlphabeticStr (type)

isAlphabeticString

  • isAlphabeticString
  • isOptionalAlphabeticString
  • isNullableAlphabeticString
  • isNullishAlphabeticString
  • TAlphaNumericStr (type)

Complex Validators

These require an initialization step which will return a validator function.

isInArray

  • isInArray
  • isOptionalInArray
  • isNullableInArray
  • isNullishInArray

Does the argument strictly equal any item in the array:

  const isInArrTest = isInArray(['1', '2', '3']);
  isInArrTest('1'); // => true

isInRange

  • 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

  • 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

  • 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

  • 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



Utilities

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';

Simple Utilities

nonNullable

Remove null/undefined from type-predicates and runtime validation:

  const isString = nonNullable(isNullishString);
  isString(null); // false
  isString(undefined); // false

transform

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

  • 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

  • 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

Validating object schemas

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.

parseObject

  • 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' },
  ]);

testObject

  • 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',
    //   }
    // }
  }

Combining parseObject and testObject

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 the options/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),
});

Custom Validators

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',
  });

Wrapping parseObject/testObject functions

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
});

Safety settings for parseObject

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
  }),
});

deepCompare and customDeepCompare

deepCompare

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.

customDeepCompare

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") or customDeepCompare("options", "callback")

The options object
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, set disregardDateException: 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 to Date 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 with rec: false. I find this option especially helpful if work a lot with IO objects were Dates 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
The callback function
(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

About

A list common typescript validator-functions and some useful utilities to go with them.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published