decoders

Choices

Decoders for union types and tagged unions.

either

function either( Decoder<A>, Decoder<B>, ... ): Decoder<A | B | ...> (source)

Accepts values accepted by any of the given decoders.

The decoders are tried on the input one by one, in the given order. The first one that accepts the input "wins". If all decoders reject the input, the input gets rejected.

Try it
either(number, string).verify(input)
InputResult
"hello world"
123
false ^^^^^ Either: - Must be number - Must be string

oneOf

function oneOf( values: T[] ): Decoder<T> (source)

Accepts any value that is strictly-equal (using ===) to one of the specified values.

Try it
oneOf(['foo', 'bar', 3]).verify(input)
InputResult
"foo"
3
"hello" ^^^^^^^ Must be one of 'foo', 'bar', 3
4 ^ Must be one of 'foo', 'bar', 3
false ^^^^^ Must be one of 'foo', 'bar', 3

For example, given an array of strings, like so:

oneOf(['foo', 'bar']);

taggedUnion

function taggedUnion(
  field: string,
  mapping: { value1: Decoder<A>, value2: Decoder<B>, ... }
): Decoder<A | B | ...> (source)

If you are decoding tagged unions you may want to use the taggedUnion() decoder instead of the general purpose either() decoder to get better error messages and better performance.

This decoder is optimized for tagged unions (also known as discriminated unions), i.e. a union of objects where one field is used as the discriminator.

Decoding now works in two steps:

  1. Look at the discriminator field in the incoming object (this is the field that decides which decoder will be used)
  2. If the value is 'A', then decoder A will be used. If it's 'B', then decoder B will be used. Otherwise, this will fail.

This is effectively equivalent to either(A, B), but will provide better error messages and is more performant at runtime because it doesn't have to try all decoders one by one.

Try it
const A = object({ type: constant('A'), foo: string });const B = object({ type: constant('B'), bar: number });taggedUnion('type', { A, B }).verify(input)
InputResult
{ "type": "A", "foo": "hello" }
{ "type": "B", "bar": 42 }
{ "type": "A", "foo": 42, ^^ Must be string }
{ "type": "C", ^^^ Must be one of 'A', 'B' }
{ "foo": "hello", } ^ Missing key: 'type'

select

function select(
  scout: Decoder<T>,
  selectFn: (result: T) => Decoder<A> | Decoder<B> | ...
): Decoder<A | B | ...> (source)

Briefly peek at a runtime input using a "scout" decoder first, then decide which decoder to run on the (original) input, based on the information that the "scout" extracted.

It serves a similar purpose as taggedUnion(), but is a generalization that works even if there isn't a single discriminator, or the discriminator isn't a string.

const decoder = select(
  // First, validate/extract the minimal information to make a decision
  object({ version: optional(number) }),

  // Then select which decoder to run
  (obj) => {
    switch (obj.version) {
      case undefined:
        return v1Decoder; // Suppose v1 doesn't have a discriminating field
      case 2:
        return v2Decoder;
      case 3:
        return v3Decoder;
      default:
        return never('Invalid version');
    }
  },
);
// Decoder<V1 | V2 | V3>

On this page