decoders

Unions

Decoders for union types, enums, and tagged unions.

either

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.

const decoder = either(number, string);

// 👍
decoder.verify('hello world') === 'hello world';
decoder.verify(123) === 123;

// 👎
decoder.verify(false);  // throws

oneOf

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

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

const decoder = oneOf(['foo', 'bar', 3]);

// 👍
decoder.verify('foo') === 'foo';
decoder.verify(3) === 3;

// 👎
decoder.verify('hello');  // throws
decoder.verify(4);        // throws
decoder.verify(false);    // throws

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

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

enum_

enum_(enum: MyEnum): Decoder<MyEnum> (source)

Accepts and return an enum value.

It works with numeric enums:

enum Fruit {
  Apple,
  Banana,
  Cherry
}

const decoder = enum_(Fruit);

// 👍
decoder.verify(Fruit.Apple) === Fruit.Apple;
decoder.verify(Fruit.Banana) === Fruit.Banana;
decoder.verify(Fruit.Cherry) === Fruit.Cherry;
decoder.verify(0) === Fruit.Apple;
decoder.verify(1) === Fruit.Banana;
decoder.verify(2) === Fruit.Cherry;

// 👎
decoder.verify('Apple');  // throws
decoder.verify(-1);       // throws
decoder.verify(3);        // throws

As well as with string enums:

enum Fruit {
  Apple = 'a',
  Banana = 'b',
  Cherry = 'c'
}

const decoder = enum_(Fruit);

// 👍
decoder.verify(Fruit.Apple) === Fruit.Apple;
decoder.verify(Fruit.Banana) === Fruit.Banana;
decoder.verify(Fruit.Cherry) === Fruit.Cherry;
decoder.verify('a') === Fruit.Apple;
decoder.verify('b') === Fruit.Banana;
decoder.verify('c') === Fruit.Cherry;

// 👎
decoder.verify('Apple');  // throws
decoder.verify(0);        // throws
decoder.verify(1);        // throws
decoder.verify(2);        // throws
decoder.verify(3);        // throws

taggedUnion

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, i.e. a union of objects where one field is used as the discriminator.

const A = object({ tag: constant('A'), foo: string });
const B = object({ tag: constant('B'), bar: number });

const AorB = taggedUnion('tag', { A, B });
//                        ^^^

Decoding now works in two steps:

  1. Look at the 'tag' 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.


select

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