Link Search Menu Expand Document (external link)

You're looking at the documentation for decoders v2.x!
You can find the old 1.x docs here, or read the migration instructions.

Decoder<T> methods

All decoders have the following methods.


# .verify(blob: mixed): T (source)

Verifies the untrusted/unknown input and either accepts or rejects it. When accepted, returns a value of type T. Otherwise fail with a runtime error.

The .verify() method explained

For example, take this simple number decoder.

// πŸ‘
number.verify(123);     // 123
number.verify(3.1415);  // 3.1415

// πŸ‘Ž
number.verify('hello'); // throws
// Decoding error:
// "hello"
// ^^^^^^^ Must be number

# .value(blob: mixed): T | undefined (source)

Verifies the untrusted/unknown input and either accepts or rejects it. When accepted, returns the decoded T value directly. Otherwise returns undefined.

Use this when you’re not interested in programmatically handling the error message.

The .value() method explained

// πŸ‘
number.value(3);     // 3
string.value('hi');  // 'hi'

// πŸ‘Ž
number.value('hi');  // undefined
string.value(42);    // undefined

NOTE: When you use this on optional() decoders, you cannot distinguish a rejected value from an accepted undefined input value.


# .decode(blob: mixed): DecodeResult<T> (source)

Verifies the untrusted/unknown input and either accepts or rejects it.

Contrasted with .verify(), calls to .decode() will never fail and instead return a result type.

The .decode() method explained

For example, take this simple β€œnumber” decoder. When given an number value, it will return an ok: true result. Otherwise, it will return an ok: false result with the original input value annotated.

// πŸ‘
number.decode(3);     // { ok: true, value: 3 };

// πŸ‘Ž
number.decode('hi');  // { ok: false, error: { type: 'scalar', value: 'hi', text: 'Must be number' } }

# .transform<V>(transformFn: (T) => V): Decoder<V> (source)

Accepts any value the given decoder accepts, and on success, will call the given function on the decoded result. If the transformation function throws an error, the whole decoder will fail using the error message as the failure reason.

const upper = string.transform((s) => s.toUpperCase());

// πŸ‘
upper.verify('foo') === 'FOO'

// πŸ‘Ž
upper.verify(4);  // throws

# .refine(predicate: T => boolean, message: string): Decoder<T> (source)

Adds an extra predicate to a decoder. The new decoder is like the original decoder, but only accepts values that also meet the predicate.

const odd = number.refine(
  (n) => n % 2 !== 0,
  'Must be odd'
);

// πŸ‘
odd.verify(3) === 3;

// πŸ‘Ž
odd.verify(42);    // throws: not an odd number
odd.verify('hi');  // throws: not a number

In TypeScript, if you provide a predicate that also is a type predicate, then this will be reflected in the return type, too.


# .reject(rejectFn: T => string | null): Decoder<T> (source)

Adds an extra predicate to a decoder. The new decoder is like the original decoder, but only accepts values that aren’t rejected by the given function.

The given function can return null to accept the decoded value, or return a specific error message to reject.

Unlike .refine(), you can use this function to return a dynamic error message.

const decoder = pojo
  .reject(
    obj => {
      const badKeys = Object.keys(obj).filter(key => key.startsWith('_'));
      return badKeys.length > 0
        ? `Disallowed keys: ${badKeys.join(', ')}`
        : null;
    }
  );

// πŸ‘
decoder.verify({ id: 123, name: 'Vincent' }) === { id: 123, name: 'Vincent' };

// πŸ‘Ž
decoder.verify({ id: 123, _name: 'Vincent'  })   // throws: "Disallowed keys: _name"

# .describe(message: string): Decoder<T> (source)

Uses the given decoder, but will use an alternative error message in case it rejects. This can be used to simplify or shorten otherwise long or low-level/technical errors.

const decoder = either(
    constant('a'),
    constant('e'),
    constant('i'),
    constant('o'),
    constant('u'),
);
const vowel = decoder.describe('Must be vowel');

# .then<V>(next: (blob: T, ok, err) => DecodeResult<V>): Decoder<V> (source)

Chain together the current decoder with another.

NOTE: This is an advanced, low-level, API. It’s not recommended to reach for this construct unless there is no other way. Most cases can be covered more elegantly by .transform() or .refine() instead.

If the current decoder accepts an input, the resulting T value will get passed into the given next acceptance function to further decide whether or not the value should get accepted or rejected.

This works similar to how you would define() a new decoder, except that the blob param will now be T (a known type), rather than unknown. This will allow the function to make a stronger assumption about its input and avoid re-refining inputs.

If it helps, you can think of define(...) as equivalent to unknown.then(...).