UNPKG

@mojotech/json-type-validation

Version:

runtime type checking and validation of untyped JSON data

541 lines (540 loc) 22.3 kB
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>; }