Skip to main content
Accepts whatever the given decoder accepts, or undefined.

Signature

function optional<T>(decoder: Decoder<T>): Decoder<T | undefined>

function optional<T, C extends Scalar>(
  decoder: Decoder<T>,
  defaultValue: (() => C) | C
): Decoder<NonNullable<T> | C>

function optional<T, V>(
  decoder: Decoder<T>,
  defaultValue: (() => V) | V
): Decoder<NonNullable<T> | V>

Type Inference

optional() creates a union type that includes undefined:
const decoder = optional(string);
// Type: Decoder<string | undefined>

const result = decoder.verify('hello');
// result is 'hello' | undefined

Basic Usage

import { optional, string } from 'decoders';

const decoder = optional(string);

decoder.verify('hello');     // 'hello'
decoder.verify(undefined);   // undefined
decoder.verify(null);        // DecodeError

With Default Value

If a default value is explicitly provided, return that instead in the undefined case:
import { optional, string } from 'decoders';

const decoder = optional(string, 'default');

decoder.verify('hello');     // 'hello'
decoder.verify(undefined);   // 'default'
decoder.verify(null);        // DecodeError

Lazy Default Values

Default values can be functions that are evaluated lazily:
import { optional, array, number } from 'decoders';

const decoder = optional(array(number), () => []);

decoder.verify([1, 2, 3]);   // [1, 2, 3]
decoder.verify(undefined);   // []
This is useful to avoid creating new objects/arrays for every decoder instance.

Union Behavior

optional() is implemented using either(undefined_, decoder), creating a true union type:
// These are equivalent:
optional(string)
either(undefined_, string)

// With union types, order matters for type inference:
const userDecoder = object({
  name: string,
  nickname: optional(string),
});

type User = DecoderType<typeof userDecoder>;
// { name: string; nickname: string | undefined }

Implementation

Source: ~/workspace/source/src/basics.ts:33-44
export function optional<T, V>(
  decoder: Decoder<T>,
  defaultValue?: (() => V) | V,
): Decoder<T | V | undefined> {
  const rv = either(undefined_, decoder);
  return arguments.length >= 2
    ? rv.transform((value) => value ?? lazyval(defaultValue as (() => V) | V))
    : rv;
}

Build docs developers (and LLMs) love