@mojotech/json-type-validation
Version:
runtime type checking and validation of untyped JSON data
541 lines (540 loc) • 22.3 kB
TypeScript
import * as Result from './result';
/**
* Information describing how json data failed to match a decoder.
* Includes the full input json, since in most cases it's useless to know how a
* decoder failed without also seeing the malformed data.
*/
export interface DecoderError {
kind: 'DecoderError';
input: unknown;
at: string;
message: string;
}
/**
* Defines a mapped type over an interface `A`. `DecoderObject<A>` is an
* interface that has all the keys or `A`, but each key's property type is
* mapped to a decoder for that type. This type is used when creating decoders
* for objects.
*
* Example:
* ```
* interface X {
* a: boolean;
* b: string;
* }
*
* const decoderObject: DecoderObject<X> = {
* a: boolean(),
* b: string()
* }
* ```
*/
export declare type DecoderObject<A> = {
[t in keyof A]: Decoder<A[t]>;
};
/**
* Type guard for `DecoderError`. One use case of the type guard is in the
* `catch` of a promise. Typescript types the error argument of `catch` as
* `any`, so when dealing with a decoder as a promise you may need to
* distinguish between a `DecoderError` and an error string.
*/
export declare const isDecoderError: (a: any) => a is DecoderError;
/**
* Decoders transform json objects with unknown structure into known and
* verified forms. You can create objects of type `Decoder<A>` with either the
* primitive decoder functions, such as `boolean()` and `string()`, or by
* applying higher-order decoders to the primitives, such as `array(boolean())`
* or `dict(string())`.
*
* Each of the decoder functions are available both as a static method on
* `Decoder` and as a function alias -- for example the string decoder is
* defined at `Decoder.string()`, but is also aliased to `string()`. Using the
* function aliases exported with the library is recommended.
*
* `Decoder` exposes a number of 'run' methods, which all decode json in the
* same way, but communicate success and failure in different ways. The `map`
* and `andThen` methods modify decoders without having to call a 'run' method.
*
* Alternatively, the main decoder `run()` method returns an object of type
* `Result<A, DecoderError>`. This library provides a number of helper
* functions for dealing with the `Result` type, so you can do all the same
* things with a `Result` as with the decoder methods.
*/
export declare class Decoder<A> {
private decode;
/**
* The Decoder class constructor is kept private to separate the internal
* `decode` function from the external `run` function. The distinction
* between the two functions is that `decode` returns a
* `Partial<DecoderError>` on failure, which contains an unfinished error
* report. When `run` is called on a decoder, the relevant series of `decode`
* calls is made, and then on failure the resulting `Partial<DecoderError>`
* is turned into a `DecoderError` by filling in the missing information.
*
* While hiding the constructor may seem restrictive, leveraging the
* provided decoder combinators and helper functions such as
* `andThen` and `map` should be enough to build specialized decoders as
* needed.
*/
private constructor();
/**
* Decoder primitive that validates strings, and fails on all other input.
*/
static string(): Decoder<string>;
/**
* Decoder primitive that validates numbers, and fails on all other input.
*/
static number(): Decoder<number>;
/**
* Decoder primitive that validates booleans, and fails on all other input.
*/
static boolean(): Decoder<boolean>;
/**
* Escape hatch to bypass validation. Always succeeds and types the result as
* `any`. Useful for defining decoders incrementally, particularly for
* complex objects.
*
* Example:
* ```
* interface User {
* name: string;
* complexUserData: ComplexType;
* }
*
* const userDecoder: Decoder<User> = object({
* name: string(),
* complexUserData: anyJson()
* });
* ```
*/
static anyJson: () => Decoder<any>;
/**
* Decoder identity function which always succeeds and types the result as
* `unknown`.
*/
static unknownJson: () => Decoder<unknown>;
/**
* Decoder primitive that only matches on exact values.
*
* Note that `constant('string to match')` returns a `Decoder<string>` which
* fails if the input is not equal to `'string to match'`. In many cases this
* is sufficient, but in some situations typescript requires that the decoder
* type be a type-literal. In such a case you must provide the type parameter,
* which looks like `constant<'string to match'>('string to match')`.
*
* Providing the type parameter is only necessary for type-literal strings
* and numbers, as detailed by this table:
*
* ```
* | Decoder | Type |
* | ---------------------------- | ---------------------|
* | constant(true) | Decoder<true> |
* | constant(false) | Decoder<false> |
* | constant(null) | Decoder<null> |
* | constant('alaska') | Decoder<string> |
* | constant<'alaska'>('alaska') | Decoder<'alaska'> |
* | constant(50) | Decoder<number> |
* | constant<50>(50) | Decoder<50> |
* | constant([1,2,3]) | Decoder<number[]> |
* | constant<[1,2,3]>([1,2,3]) | Decoder<[1,2,3]> |
* | constant({x: 't'}) | Decoder<{x: string}> |
* | constant<{x: 't'}>({x: 't'}) | Decoder<{x: 't'}> |
* ```
*
*
* One place where this happens is when a type-literal is in an interface:
* ```
* interface Bear {
* kind: 'bear';
* isBig: boolean;
* }
*
* const bearDecoder1: Decoder<Bear> = object({
* kind: constant('bear'),
* isBig: boolean()
* });
* // Type 'Decoder<{ kind: string; isBig: boolean; }>' is not assignable to
* // type 'Decoder<Bear>'. Type 'string' is not assignable to type '"bear"'.
*
* const bearDecoder2: Decoder<Bear> = object({
* kind: constant<'bear'>('bear'),
* isBig: boolean()
* });
* // no compiler errors
* ```
*
* Another is in type-literal unions:
* ```
* type animal = 'bird' | 'bear';
*
* const animalDecoder1: Decoder<animal> = union(
* constant('bird'),
* constant('bear')
* );
* // Type 'Decoder<string>' is not assignable to type 'Decoder<animal>'.
* // Type 'string' is not assignable to type 'animal'.
*
* const animalDecoder2: Decoder<animal> = union(
* constant<'bird'>('bird'),
* constant<'bear'>('bear')
* );
* // no compiler errors
* ```
*/
static constant(value: true): Decoder<true>;
static constant(value: false): Decoder<false>;
static constant<A>(value: A): Decoder<A>;
/**
* An higher-order decoder that runs decoders on specified fields of an object,
* and returns a new object with those fields. If `object` is called with no
* arguments, then the outer object part of the json is validated but not the
* contents, typing the result as a record where all keys have a value of
* type `unknown`.
*
* The `optional` and `constant` decoders are particularly useful for decoding
* objects that match typescript interfaces.
*
* To decode a single field that is inside of an object see `valueAt`.
*
* Example:
* ```
* object({x: number(), y: number()}).run({x: 5, y: 10})
* // => {ok: true, result: {x: 5, y: 10}}
*
* object().map(Object.keys).run({n: 1, i: [], c: {}, e: 'e'})
* // => {ok: true, result: ['n', 'i', 'c', 'e']}
* ```
*/
static object(): Decoder<Record<string, unknown>>;
static object<A>(decoders: DecoderObject<A>): Decoder<A>;
/**
* Decoder for json arrays. Runs `decoder` on each array element, and succeeds
* if all elements are successfully decoded. If no `decoder` argument is
* provided then the outer array part of the json is validated but not the
* contents, typing the result as `unknown[]`.
*
* To decode a single value that is inside of an array see `valueAt`.
*
* Examples:
* ```
* array(number()).run([1, 2, 3])
* // => {ok: true, result: [1, 2, 3]}
*
* array(array(boolean())).run([[true], [], [true, false, false]])
* // => {ok: true, result: [[true], [], [true, false, false]]}
*
*
* const validNumbersDecoder = array()
* .map((arr: unknown[]) => arr.map(number().run))
* .map(Result.successes)
*
* validNumbersDecoder.run([1, true, 2, 3, 'five', 4, []])
* // {ok: true, result: [1, 2, 3, 4]}
*
* validNumbersDecoder.run([false, 'hi', {}])
* // {ok: true, result: []}
*
* validNumbersDecoder.run(false)
* // {ok: false, error: {..., message: "expected an array, got a boolean"}}
* ```
*/
static array(): Decoder<unknown[]>;
static array<A>(decoder: Decoder<A>): Decoder<A[]>;
/**
* Decoder for fixed-length arrays, aka Tuples.
*
* Supports up to 8-tuples.
*
* Example:
* ```
* tuple([number(), number(), string()]).run([5, 10, 'px'])
* // => {ok: true, result: [5, 10, 'px']}
* ```
*/
static tuple<A>(decoder: [Decoder<A>]): Decoder<[A]>;
static tuple<A, B>(decoder: [Decoder<A>, Decoder<B>]): Decoder<[A, B]>;
static tuple<A, B, C>(decoder: [Decoder<A>, Decoder<B>, Decoder<C>]): Decoder<[A, B, C]>;
static tuple<A, B, C, D>(decoder: [Decoder<A>, Decoder<B>, Decoder<C>, Decoder<D>]): Decoder<[A, B, C, D]>;
static tuple<A, B, C, D, E>(decoder: [Decoder<A>, Decoder<B>, Decoder<C>, Decoder<D>, Decoder<E>]): Decoder<[A, B, C, D, E]>;
static tuple<A, B, C, D, E, F>(decoder: [Decoder<A>, Decoder<B>, Decoder<C>, Decoder<D>, Decoder<E>, Decoder<F>]): Decoder<[A, B, C, D, E, F]>;
static tuple<A, B, C, D, E, F, G>(decoder: [Decoder<A>, Decoder<B>, Decoder<C>, Decoder<D>, Decoder<E>, Decoder<F>, Decoder<G>]): Decoder<[A, B, C, D, E, F, G]>;
static tuple<A, B, C, D, E, F, G, H>(decoder: [Decoder<A>, Decoder<B>, Decoder<C>, Decoder<D>, Decoder<E>, Decoder<F>, Decoder<G>, Decoder<H>]): Decoder<[A, B, C, D, E, F, G, H]>;
/**
* Decoder for json objects where the keys are unknown strings, but the values
* should all be of the same type.
*
* Example:
* ```
* dict(number()).run({chocolate: 12, vanilla: 10, mint: 37});
* // => {ok: true, result: {chocolate: 12, vanilla: 10, mint: 37}}
* ```
*/
static dict: <A_1>(decoder: Decoder<A_1>) => Decoder<Record<string, A_1>>;
/**
* Decoder for values that may be `undefined`. This is primarily helpful for
* decoding interfaces with optional fields.
*
* Example:
* ```
* interface User {
* id: number;
* isOwner?: boolean;
* }
*
* const decoder: Decoder<User> = object({
* id: number(),
* isOwner: optional(boolean())
* });
* ```
*/
static optional: <A_1>(decoder: Decoder<A_1>) => Decoder<A_1 | undefined>;
/**
* Decoder that attempts to run each decoder in `decoders` and either succeeds
* with the first successful decoder, or fails after all decoders have failed.
*
* Note that `oneOf` expects the decoders to all have the same return type,
* while `union` creates a decoder for the union type of all the input
* decoders.
*
* Examples:
* ```
* oneOf(string(), number().map(String))
* oneOf(constant('start'), constant('stop'), succeed('unknown'))
* ```
*/
static oneOf: <A_1>(...decoders: Decoder<A_1>[]) => Decoder<A_1>;
/**
* Combines 2-8 decoders of disparate types into a decoder for the union of all
* the types.
*
* If you need more than 8 variants for your union, it's possible to use
* `oneOf` in place of `union` as long as you annotate every decoder with the
* union type.
*
* Example:
* ```
* type C = {a: string} | {b: number};
*
* const unionDecoder: Decoder<C> = union(object({a: string()}), object({b: number()}));
* const oneOfDecoder: Decoder<C> = oneOf(object<C>({a: string()}), object<C>({b: number()}));
* ```
*/
static union<A, B>(ad: Decoder<A>, bd: Decoder<B>): Decoder<A | B>;
static union<A, B, C>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>): Decoder<A | B | C>;
static union<A, B, C, D>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>): Decoder<A | B | C | D>;
static union<A, B, C, D, E>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>): Decoder<A | B | C | D | E>;
static union<A, B, C, D, E, F>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>, fd: Decoder<F>): Decoder<A | B | C | D | E | F>;
static union<A, B, C, D, E, F, G>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>, fd: Decoder<F>, gd: Decoder<G>): Decoder<A | B | C | D | E | F | G>;
static union<A, B, C, D, E, F, G, H>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>, fd: Decoder<F>, gd: Decoder<G>, hd: Decoder<H>): Decoder<A | B | C | D | E | F | G | H>;
/**
* Combines 2-8 object decoders into a decoder for the intersection of all the objects.
*
* Example:
* ```
* interface Pet {
* name: string;
* maxLegs: number;
* }
*
* interface Cat extends Pet {
* evil: boolean;
* }
*
* const petDecoder: Decoder<Pet> = object({name: string(), maxLegs: number()});
* const catDecoder: Decoder<Cat> = intersection(petDecoder, object({evil: boolean()}));
* ```
*/
static intersection<A, B>(ad: Decoder<A>, bd: Decoder<B>): Decoder<A & B>;
static intersection<A, B, C>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>): Decoder<A & B & C>;
static intersection<A, B, C, D>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>): Decoder<A & B & C & D>;
static intersection<A, B, C, D, E>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>): Decoder<A & B & C & D & E>;
static intersection<A, B, C, D, E, F>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>, fd: Decoder<F>): Decoder<A & B & C & D & E & F>;
static intersection<A, B, C, D, E, F, G>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>, fd: Decoder<F>, gd: Decoder<G>): Decoder<A & B & C & D & E & F & G>;
static intersection<A, B, C, D, E, F, G, H>(ad: Decoder<A>, bd: Decoder<B>, cd: Decoder<C>, dd: Decoder<D>, ed: Decoder<E>, fd: Decoder<F>, gd: Decoder<G>, hd: Decoder<H>): Decoder<A & B & C & D & E & F & G & H>;
/**
* Decoder that always succeeds with either the decoded value, or a fallback
* default value.
*/
static withDefault: <A_1>(defaultValue: A_1, decoder: Decoder<A_1>) => Decoder<A_1>;
/**
* Decoder that pulls a specific field out of a json structure, instead of
* decoding and returning the full structure. The `paths` array describes the
* object keys and array indices to traverse, so that values can be pulled out
* of a nested structure.
*
* Example:
* ```
* const decoder = valueAt(['a', 'b', 0], string());
*
* decoder.run({a: {b: ['surprise!']}})
* // => {ok: true, result: 'surprise!'}
*
* decoder.run({a: {x: 'cats'}})
* // => {ok: false, error: {... at: 'input.a.b[0]' message: 'path does not exist'}}
* ```
*
* Note that the `decoder` is ran on the value found at the last key in the
* path, even if the last key is not found. This allows the `optional`
* decoder to succeed when appropriate.
* ```
* const optionalDecoder = valueAt(['a', 'b', 'c'], optional(string()));
*
* optionalDecoder.run({a: {b: {c: 'surprise!'}}})
* // => {ok: true, result: 'surprise!'}
*
* optionalDecoder.run({a: {b: 'cats'}})
* // => {ok: false, error: {... at: 'input.a.b.c' message: 'expected an object, got "cats"'}
*
* optionalDecoder.run({a: {b: {z: 1}}})
* // => {ok: true, result: undefined}
* ```
*/
static valueAt: <A_1>(paths: (string | number)[], decoder: Decoder<A_1>) => Decoder<A_1>;
/**
* Decoder that ignores the input json and always succeeds with `fixedValue`.
*/
static succeed: <A_1>(fixedValue: A_1) => Decoder<A_1>;
/**
* Decoder that ignores the input json and always fails with `errorMessage`.
*/
static fail: <A_1>(errorMessage: string) => Decoder<A_1>;
/**
* Decoder that allows for validating recursive data structures. Unlike with
* functions, decoders assigned to variables can't reference themselves
* before they are fully defined. We can avoid prematurely referencing the
* decoder by wrapping it in a function that won't be called until use, at
* which point the decoder has been defined.
*
* Example:
* ```
* interface Comment {
* msg: string;
* replies: Comment[];
* }
*
* const decoder: Decoder<Comment> = object({
* msg: string(),
* replies: lazy(() => array(decoder))
* });
* ```
*/
static lazy: <A_1>(mkDecoder: () => Decoder<A_1>) => Decoder<A_1>;
/**
* Run the decoder and return a `Result` with either the decoded value or a
* `DecoderError` containing the json input, the location of the error, and
* the error message.
*
* Examples:
* ```
* number().run(12)
* // => {ok: true, result: 12}
*
* string().run(9001)
* // =>
* // {
* // ok: false,
* // error: {
* // kind: 'DecoderError',
* // input: 9001,
* // at: 'input',
* // message: 'expected a string, got 9001'
* // }
* // }
* ```
*/
run: (json: unknown) => Result.Result<A, DecoderError>;
/**
* Run the decoder as a `Promise`.
*/
runPromise: (json: unknown) => Promise<A>;
/**
* Run the decoder and return the value on success, or throw an exception
* with a formatted error string.
*/
runWithException: (json: unknown) => A;
/**
* Construct a new decoder that applies a transformation to the decoded
* result. If the decoder succeeds then `f` will be applied to the value. If
* it fails the error will propagated through.
*
* Example:
* ```
* number().map(x => x * 5).run(10)
* // => {ok: true, result: 50}
* ```
*/
map: <B>(f: (value: A) => B) => Decoder<B>;
/**
* Chain together a sequence of decoders. The first decoder will run, and
* then the function will determine what decoder to run second. If the result
* of the first decoder succeeds then `f` will be applied to the decoded
* value. If it fails the error will propagate through.
*
* This is a very powerful method -- it can act as both the `map` and `where`
* methods, can improve error messages for edge cases, and can be used to
* make a decoder for custom types.
*
* Example of adding an error message:
* ```
* const versionDecoder = valueAt(['version'], number());
* const infoDecoder3 = object({a: boolean()});
*
* const decoder = versionDecoder.andThen(version => {
* switch (version) {
* case 3:
* return infoDecoder3;
* default:
* return fail(`Unable to decode info, version ${version} is not supported.`);
* }
* });
*
* decoder.run({version: 3, a: true})
* // => {ok: true, result: {a: true}}
*
* decoder.run({version: 5, x: 'abc'})
* // =>
* // {
* // ok: false,
* // error: {... message: 'Unable to decode info, version 5 is not supported.'}
* // }
* ```
*
* Example of decoding a custom type:
* ```
* // nominal type for arrays with a length of at least one
* type NonEmptyArray<T> = T[] & { __nonEmptyArrayBrand__: void };
*
* const nonEmptyArrayDecoder = <T>(values: Decoder<T>): Decoder<NonEmptyArray<T>> =>
* array(values).andThen(arr =>
* arr.length > 0
* ? succeed(createNonEmptyArray(arr))
* : fail(`expected a non-empty array, got an empty array`)
* );
* ```
*/
andThen: <B>(f: (value: A) => Decoder<B>) => Decoder<B>;
/**
* Add constraints to a decoder _without_ changing the resulting type. The
* `test` argument is a predicate function which returns true for valid
* inputs. When `test` fails on an input, the decoder fails with the given
* `errorMessage`.
*
* ```
* const chars = (length: number): Decoder<string> =>
* string().where(
* (s: string) => s.length === length,
* `expected a string of length ${length}`
* );
*
* chars(5).run('12345')
* // => {ok: true, result: '12345'}
*
* chars(2).run('HELLO')
* // => {ok: false, error: {... message: 'expected a string of length 2'}}
*
* chars(12).run(true)
* // => {ok: false, error: {... message: 'expected a string, got a boolean'}}
* ```
*/
where: (test: (value: A) => boolean, errorMessage: string) => Decoder<A>;
}