Skip to content

Guards

Guards are functions that accepts one unknown argument and returns a type predicate:

ts
type Guard<T> = (data) => data is T

If the function returns true, TypeScript will narrow the type of the data argument to T; for example:

ts
import { isString, isNumber, objectGuard as object } from 'pure-parse'
import data from 'my-data.json'

const isUser = object({
  name: isString,
  age: isNumber,
})

if (isUser(data)) {
  console.log(`The user's name is "${data.name}"`)
} else {
  console.error('The data does not describe a user')
}

TIP

For a full reference, see the API documentation on parsers.

Overview

PureParse exports two categories of functions related to type guarding.

First, there are type guards. Each primitive value and reference type has a corresponding parser, where the most useful ones are:

Secondly, there is a category of higher order functions that constructs new guards based on parameters:

By composing these higher order functions and primitives, you end up with a schema-like syntax that models your data:

ts
import {
  isNumber,
  isString,
  objectGuard as object,
  optionalGuard as optional,
} from 'pure-parse'

const isUsers = arrays(
  object({
    id: isNumber,
    parentId: nullable(isNumber),
    name: isString,
    address: optional(
      object({
        country: isString,
        city: isString,
        streetAddress: isString,
        zipCode: isNumber,
      }),
    ),
  }),
)

TIP

See the API Reference documentation for a complete inventory

Primitives

Primitive types represent primitive values, which are immutable and have no properties:

ts
isString('hello') // -> true
isNumber(42) // -> true
isBoolean(true) // -> true
isNull(null) // -> true
isUndefined(undefined) // -> true
isBigInt(42n) // -> true
isSymbol(Symbol()) // -> true

Equality Checks for Primitive Literals

Primitive literals such as true, false, 42, "hello", and null are all types and values (depending on the context they appear in). Use the equalsGuard() function to create a guard function that compares the data with the strict equality operator (===):

ts
const isRed = equalsGuard('red')
isRed('red') // -> true
isRed('blue') // -> false

const isOne = equalsGuard(1)
isOne(1) // -> true
isOne(2) // -> false

When called with multiple arguments, equalsGuard() validates a union:

ts
const isDirection = equalsGuard('north', 'south', 'east', 'west')
isDirection('north') // -> true
isDirection('east') // -> true

const isDigit = equalsGuard(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
isDigit(5) // -> true
isDigit(100) // -> false

Unions

Unions types—or sum types—represent values that can be one of several types. Use the oneOfGuard() function to create a validation function for a union type:

ts
const isStringOrNumber = unionGuard(isString, isNumber)
isStringOrNumber('hello') // -> true
isStringOrNumber(123) // -> true

Since it is very common to create unions with null and undefined, there are two helper functions for validating optional and nullable types:

  • undefineableGuard—for unions with undefined
  • nullableGuard—for unions with null
ts
const isUndefineableString = undefineableGuard(isString)
isOptionalString('hello') // -> true
isOptionalString(undefined) // -> true

const isNullableString = optionalGuard(isString)
isNullableString('hello') // -> true
isNullableString(null) // -> true

Tuples

Tuples are arrays of fixed length, where each element has a specific type. Use the tupleGuard() function to create a guard function for a tuple type:

ts
const isCoordinate = tupleGuard([isNumber, isNumber])
isCoordinate([42, 34]) // -> true

const isTransparentColor = tupleGuard([isString, isNumber])
isTransparentColor(['#FF0000', 0.5]) // -> true

Objects

Validate objects with the objectGuard() function which takes an object with keys and corresponding validation functions:

ts
const isUser = objectGuard({
  id: isNumber,
  name: isString,
})
isUser({ id: 42, name: 'Alice' }) // -> true

You can nest objects:

ts
const isUser = objectGuard({
  id: isNumber,
  name: isString,
  address: objectGuard({
    country: isString,
    city: isString,
    streetAddress: isString,
    zipCode: isNumber,
  }),
})

You can declare optional properties:

ts
const isUser = objectGuard({
  id: isNumber,
  name: optionalGuard(isString),
})
isUser({ id: 42 }) // -> true
isUser({ id: 42, name: undefined }) // -> true
isUser({ id: 42, name: 'Jiří' }) // -> true

You can explicitly declare the type of the object and annotate the validation function with the type as a type parameter:

ts
type User = {
  id: number
  name?: string
}
const isUser = objectGuard<User>({
  id: isNumber,
  name: optionalGuard(isString),
})

Arrays

Arrays are ordered sets of elements of the same type. Use the arrayGuard() function to create a validation function for an arrays type:

ts
const isBase = equalsGuard('A', 'T', 'C', 'G')
const isDna = arrayGuard(isBase)
isDna(['A', 'T', 'A', 'T', 'C', 'G']) // -> true

When explicitly declaring arrays types, provide type of the item in the arrays type argument:

ts
// Guard<number[]>
const isNumberArray = arrayGuard<number>(isNumber)

Tagged/Discriminated Unions

Validate discriminated unions with unions of objects with a common tag property:

ts
const isState = unionGuard(
  objectGuard({
    tag: equalsGuard('loading'),
  }),
  objectGuard({
    tag: equalsGuard('error'),
    error: isString,
  }),
  objectGuard({
    tag: equalsGuard('loaded'),
    message: isString,
  }),
)
isState({ tag: 'loading' }) // -> true
isState({ tag: 'error', error: 'Failed to load' }) // -> true
isState({ tag: 'loaded', message: 'Data loaded' }) // -> true