Repository: mojotech/json-type-validation Branch: master Commit: 091844b153c2 Files: 34 Total size: 135.3 KB Directory structure: gitextract_0gr7gt1_/ ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── DOCS.md ├── LICENSE ├── README.md ├── bin/ │ └── jtv ├── docs/ │ ├── README.md │ ├── classes/ │ │ └── _decoder_.decoder.md │ ├── interfaces/ │ │ ├── _decoder_.decodererror.md │ │ ├── _result_.err.md │ │ └── _result_.ok.md │ └── modules/ │ ├── _combinators_.md │ ├── _decoder_.md │ ├── _index_.md │ └── _result_.md ├── nix-files/ │ ├── default.nix │ ├── nixpkgs.nix │ └── shell.nix ├── package.json ├── rollup.config.ts ├── src/ │ ├── combinators.ts │ ├── decoder.ts │ ├── index.ts │ └── result.ts ├── test/ │ ├── json-decode.test.ts │ ├── phone-example.test.ts │ ├── tagged-json-example.test.ts │ └── user-example.test.ts ├── tsconfig-test.json ├── tsconfig.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules coverage .nyc_output .DS_Store *.log .vscode .idea dist compiled .awcache .rpt2_cache gc-roots ================================================ FILE: .npmignore ================================================ .git/ .rpt2_cache/ bin/ coverage/ nix-files/ node_modules/ src/ test/ .gitignore .prettierrc .travis.yml rollup.config.ts tags tsconfig-test.json tsconfig.json tslint.json yarn.lock ================================================ FILE: .prettierignore ================================================ package.json dist/ ================================================ FILE: .prettierrc ================================================ printWidth: 100 tabWidth: 2 useTabs: false semi: true singleQuote: true trailingComma: none bracketSpacing: false arrowParens: avoid parser: typescript ================================================ FILE: .travis.yml ================================================ language: node_js cache: yarn: true directories: - node_modules notifications: email: false node_js: - node script: - npm run test:prod && npm run build after_success: - npm run report-coverage ================================================ FILE: DOCS.md ================================================ # Documentation [Documentation](https://github.com/mojotech/json-type-validation/tree/master/docs). The best places to start are with the examples in the `test/` directory, and the documentation for the [Decoder class](https://github.com/mojotech/json-type-validation/blob/master/docs/classes/_decoder_.decoder.md). At some point you may need documentation for dealing with the [Result type](https://github.com/mojotech/json-type-validation/blob/master/docs/modules/_result_.md). ### Type Parameters Many of the decoder functions take an optional type parameter which determines the type of the decoded value. In most cases typescript successfully infers these types, although some specific decoders include documentation for situations where the type is necessary (see the `constant` and `union` decoders). You may still find that including the type parameter improves type inference in situations where typescript's error messages are particularly unhelpful. As an example, a decoder for the `Pet` interface can be typechecked just as effectively using the type parameter as with the `Decoder` annotation. ``` const petDecoder = object({ name: string(), species: string(), age: optional(number()), isCute: optional(boolean()) }) ``` ### Combinators This library uses the [combinator pattern](https://wiki.haskell.org/Combinator_pattern) to build decoders. The decoder primitives `string`, `number`, `boolean`, `anyJson`, `constant`, `succeed`, and `fail` act as decoder building blocks that each perform a simple decoding operation. The decoder combinators `object`, `array`, `dict`, `optional`, `oneOf`, `union`, `withDefault`, `valueAt`, and `lazy` take decoders as arguments, and combined the decoders into more complicated structures. You can think of your own user-defined decoders as an extension of these composable units. ================================================ FILE: LICENSE ================================================ Copyright (c) 2018 Elias Mulhall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # JSON Type Validation A [TypeScript](https://www.typescriptlang.org/) library to perform type checking and validation on untyped JSON data at runtime. This library owes thanks to: - [JsonDecoder](https://github.com/aische/JsonDecoder) by Daniel van den Eijkel - [Type-safe JSON Decoder](https://github.com/ooesili/type-safe-json-decoder) by Wesley Merkel - The Elm [Json.Decode](http://package.elm-lang.org/packages/elm-lang/core/latest/Json-Decode) API ## Installation ``` npm i @mojotech/json-type-validation ``` Projects using `< typescript@3.0.1` will need a polyfill for the `unknown` type, such as [unknown-ts](https://www.npmjs.com/package/unknown-ts). ## Motivation Let's say we're creating a web app for our pet sitting business, and we've picked TypeScript as one of our core technologies. This is a great choice because the extra stability and type safety that TypeScript provides is really going to help us market our business. We've defined the data we need to track about each client's pet: ```typescript interface Pet { name: string; species: string; age?: number; isCute?: boolean; } ``` And we've got some data about current client's pets which is stored as JSON: ```typescript const croc: Pet = JSON.parse('{"name":"Lyle","species":"Crocodile","isCute":true}') const moose: Pet = JSON.parse('{"name":"Bullwinkle","age":59}') ``` But that can't be right -- our data for `moose` is missing information required for the `Pet` interface, but TypeScript compiles the code just fine! Of course this isn't an issue with TypeScript, but with our own type annotations. In TypeScript `JSON.parse` has a return type of `any`, which pushes the responsibility of verifying the type of data onto the user. By assuming that all of our data is correctly formed, we've left ourselves open to unexpected errors at runtime. Unfortunately TypeScript doesn't provide a good built-in way to deal with this issue. Providing run-time type information is one of TypeScript's [non-goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals), and our web app is too important to risk using a forked version of TypeScript with that added functionality. [Type guards](https://basarat.gitbooks.io/typescript/docs/types/typeGuard.html) work, but are limited in that they circumvent type inference instead of working with it, and can be cumbersome to write. With `json-type-validation` we can define decoders that validate untyped json input. Decoders are concise, composable, and typecheck against our defined types and interfaces. ```typescript import {Decoder, object, string, optional, number, boolean} from '@mojotech/json-type-validation' const petDecoder: Decoder = object({ name: string(), species: string(), age: optional(number()), isCute: optional(boolean()) }) ``` Finally, we can choose from a number of decoding methods to validate json and report success or failure. When some json input fails validation the decoder clearly shows how the data was malformed. ```typescript const lyle: Pet = petDecoder.runWithException(croc) const bullwinkle: Pet = petDecoder.runWithException(moose) // Throws the exception: // `Input: {"name":"Bullwinkle","age":59} // Failed at input: the key 'species' is required but was not present` ``` ## Documentation [Documentation](https://github.com/mojotech/json-type-validation/tree/master/docs). ## Building ### With Nix There exists some [Nix](https://nixos.org/nix) infrastructure that can be used to reproduce a build environment exactly. A helper shell script lives at `bin/jtv` that you can use to enter environments for multiple uses. You'll need to follow the directions on the Nix website to install and use the Nix package manager. * To enter a shell suitable for building the library run `./bin/jtv build-shell`. This will leave you in the root of the project and automatically install any project and npm dependencies. You can run further yarn commands here. * To build the library for distribution and exit you can run `./bin/jtv distribute`. * To enter a build shell and run the build process, watching for changes, run `./bin/jtv build-watch`. * To run an arbitrary command in a build environment use `./bin/jtv run COMMAND`. For example, `./bin/jtv run yarn test` will run the tests and exit. ================================================ FILE: bin/jtv ================================================ #!/usr/bin/env bash SCRIPTNAME=$(basename $0) DIRNAME=$(dirname $0) MODE=$1 shift print_help() { echo "Usage: $SCRIPTNAME MODE" echo "Enter a shell with all dependencies required for MODE." echo "MODE is one of:" echo " build-shell - enter a shell for building the library" echo " build-watch - enter a build shell and run \`yarn start\`" echo " distribute - build the library and exist" echo " run COMMAND - enter a build shell and run the specified command" echo " help, --help, -h - display this help message" } if [ "$MODE" = "help" -o "$MODE" = "--help" -o "$MODE" = "-h" ]; then print_help exit 0 fi if [ "$MODE" = "build-shell" ]; then echo "Starting build shell..." # --add-root, --indirect place a garbage collection root outside of the main # nix store directory. This allows you to run nix-collect-garbage without # collecting the dependencies for this package. To collect the dependencies # for this package, delete the roots in gc-roots. # --pure limits the generated shell to only those dependencies specified in # the nix file. nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix elif [ "$MODE" = "build-watch" ]; then echo "Starting build watcher..." nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix --run "yarn start" elif [ "$MODE" = "distribute" ]; then echo "Building library for distribution..." nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix --run "yarn build" elif [ "$MODE" = "run" ]; then echo "Running \`$@\` in build environment..." nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix --run "$*" else echo "Must specify MODE!" print_help exit 1 fi ================================================ FILE: docs/README.md ================================================ Documentation ============= [Documentation](https://github.com/mojotech/json-type-validation/tree/master/docs). The best places to start are with the examples in the `test/` directory, and the documentation for the [Decoder class](https://github.com/mojotech/json-type-validation/blob/master/docs/classes/_decoder_.decoder.md). At some point you may need documentation for dealing with the [Result type](https://github.com/mojotech/json-type-validation/blob/master/docs/modules/_result_.md). ### Type Parameters Many of the decoder functions take an optional type parameter which determines the type of the decoded value. In most cases typescript successfully infers these types, although some specific decoders include documentation for situations where the type is necessary (see the `constant` and `union` decoders). You may still find that including the type parameter improves type inference in situations where typescript's error messages are particularly unhelpful. As an example, a decoder for the `Pet` interface can be typechecked just as effectively using the type parameter as with the `Decoder` annotation. ``` const petDecoder = object({ name: string(), species: string(), age: optional(number()), isCute: optional(boolean()) }) ``` ### Combinators This library uses the [combinator pattern](https://wiki.haskell.org/Combinator_pattern) to build decoders. The decoder primitives `string`, `number`, `boolean`, `anyJson`, `constant`, `succeed`, and `fail` act as decoder building blocks that each perform a simple decoding operation. The decoder combinators `object`, `array`, `dict`, `optional`, `oneOf`, `union`, `withDefault`, `valueAt`, and `lazy` take decoders as arguments, and combined the decoders into more complicated structures. You can think of your own user-defined decoders as an extension of these composable units. ## Index ### External modules * ["combinators"](modules/_combinators_.md) * ["decoder"](modules/_decoder_.md) * ["index"](modules/_index_.md) * ["result"](modules/_result_.md) --- ================================================ FILE: docs/classes/_decoder_.decoder.md ================================================ [@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) > [Decoder](../classes/_decoder_.decoder.md) # Class: Decoder Decoders transform json objects with unknown structure into known and verified forms. You can create objects of type `Decoder` 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`. 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. ## Type parameters #### A ## Hierarchy **Decoder** ## Index ### Constructors * [constructor](_decoder_.decoder.md#constructor) ### Properties * [decode](_decoder_.decoder.md#decode) ### Methods * [andThen](_decoder_.decoder.md#andthen) * [map](_decoder_.decoder.md#map) * [run](_decoder_.decoder.md#run) * [runPromise](_decoder_.decoder.md#runpromise) * [runWithException](_decoder_.decoder.md#runwithexception) * [where](_decoder_.decoder.md#where) * [anyJson](_decoder_.decoder.md#anyjson) * [array](_decoder_.decoder.md#array) * [boolean](_decoder_.decoder.md#boolean) * [constant](_decoder_.decoder.md#constant) * [dict](_decoder_.decoder.md#dict) * [fail](_decoder_.decoder.md#fail) * [intersection](_decoder_.decoder.md#intersection) * [lazy](_decoder_.decoder.md#lazy) * [number](_decoder_.decoder.md#number) * [object](_decoder_.decoder.md#object) * [oneOf](_decoder_.decoder.md#oneof) * [optional](_decoder_.decoder.md#optional) * [string](_decoder_.decoder.md#string) * [succeed](_decoder_.decoder.md#succeed) * [tuple](_decoder_.decoder.md#tuple) * [union](_decoder_.decoder.md#union) * [unknownJson](_decoder_.decoder.md#unknownjson) * [valueAt](_decoder_.decoder.md#valueat) * [withDefault](_decoder_.decoder.md#withdefault) --- ## Constructors ### `` constructor ⊕ **new Decoder**(decode: *`function`*): [Decoder](_decoder_.decoder.md) 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` 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` 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. **Parameters:** | Param | Type | | ------ | ------ | | decode | `function` | **Returns:** [Decoder](_decoder_.decoder.md) ___ ## Properties ### `` decode **● decode**: *`function`* #### Type declaration ▸(json: *`unknown`*): `DecodeResult`<`A`> **Parameters:** | Param | Type | | ------ | ------ | | json | `unknown` | **Returns:** `DecodeResult`<`A`> ___ ## Methods ### andThen ▸ **andThen**B(f: *`function`*): [Decoder](_decoder_.decoder.md)<`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[] & { __nonEmptyArrayBrand__: void }; const nonEmptyArrayDecoder = (values: Decoder): Decoder> => array(values).andThen(arr => arr.length > 0 ? succeed(createNonEmptyArray(arr)) : fail(`expected a non-empty array, got an empty array`) ); ``` **Type parameters:** #### B **Parameters:** | Param | Type | | ------ | ------ | | f | `function` | **Returns:** [Decoder](_decoder_.decoder.md)<`B`> ___ ### map ▸ **map**B(f: *`function`*): [Decoder](_decoder_.decoder.md)<`B`> 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} ``` **Type parameters:** #### B **Parameters:** | Param | Type | | ------ | ------ | | f | `function` | **Returns:** [Decoder](_decoder_.decoder.md)<`B`> ___ ### run ▸ **run**(json: *`unknown`*): `RunResult`<`A`> 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' // } // } ``` **Parameters:** | Param | Type | | ------ | ------ | | json | `unknown` | **Returns:** `RunResult`<`A`> ___ ### runPromise ▸ **runPromise**(json: *`unknown`*): `Promise`<`A`> Run the decoder as a `Promise`. **Parameters:** | Param | Type | | ------ | ------ | | json | `unknown` | **Returns:** `Promise`<`A`> ___ ### runWithException ▸ **runWithException**(json: *`unknown`*): `A` Run the decoder and return the value on success, or throw an exception with a formatted error string. **Parameters:** | Param | Type | | ------ | ------ | | json | `unknown` | **Returns:** `A` ___ ### where ▸ **where**(test: *`function`*, errorMessage: *`string`*): [Decoder](_decoder_.decoder.md)<`A`> 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().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'}} ``` **Parameters:** | Param | Type | | ------ | ------ | | test | `function` | | errorMessage | `string` | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` anyJson ▸ **anyJson**(): [Decoder](_decoder_.decoder.md)<`any`> 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 = object({ name: string(), complexUserData: anyJson() }); ``` **Returns:** [Decoder](_decoder_.decoder.md)<`any`> ___ ### `` array ▸ **array**(): [Decoder](_decoder_.decoder.md)<`unknown`[]> ▸ **array**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`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"}} ``` **Returns:** [Decoder](_decoder_.decoder.md)<`unknown`[]> **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | **Returns:** [Decoder](_decoder_.decoder.md)<`A`[]> ___ ### `` boolean ▸ **boolean**(): [Decoder](_decoder_.decoder.md)<`boolean`> Decoder primitive that validates booleans, and fails on all other input. **Returns:** [Decoder](_decoder_.decoder.md)<`boolean`> ___ ### `` constant ▸ **constant**(value: *`true`*): [Decoder](_decoder_.decoder.md)<`true`> ▸ **constant**(value: *`false`*): [Decoder](_decoder_.decoder.md)<`false`> ▸ **constant**A(value: *`A`*): [Decoder](_decoder_.decoder.md)<`A`> Decoder primitive that only matches on exact values. Note that `constant('string to match')` returns a `Decoder` 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 | | constant(false) | Decoder | | constant(null) | Decoder | | constant('alaska') | Decoder | | constant<'alaska'>('alaska') | Decoder<'alaska'> | | constant(50) | Decoder | | constant<50>(50) | Decoder<50> | | constant([1,2,3]) | Decoder | | 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 = object({ kind: constant('bear'), isBig: boolean() }); // Type 'Decoder<{ kind: string; isBig: boolean; }>' is not assignable to // type 'Decoder'. Type 'string' is not assignable to type '"bear"'. const bearDecoder2: Decoder = object({ kind: constant<'bear'>('bear'), isBig: boolean() }); // no compiler errors ``` Another is in type-literal unions: ``` type animal = 'bird' | 'bear'; const animalDecoder1: Decoder = union( constant('bird'), constant('bear') ); // Type 'Decoder' is not assignable to type 'Decoder'. // Type 'string' is not assignable to type 'animal'. const animalDecoder2: Decoder = union( constant<'bird'>('bird'), constant<'bear'>('bear') ); // no compiler errors ``` **Parameters:** | Param | Type | | ------ | ------ | | value | `true` | **Returns:** [Decoder](_decoder_.decoder.md)<`true`> **Parameters:** | Param | Type | | ------ | ------ | | value | `false` | **Returns:** [Decoder](_decoder_.decoder.md)<`false`> **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | value | `A` | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` dict ▸ **dict**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`Record`<`string`, `A`>> 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}} ``` **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | **Returns:** [Decoder](_decoder_.decoder.md)<`Record`<`string`, `A`>> ___ ### `` fail ▸ **fail**A(errorMessage: *`string`*): [Decoder](_decoder_.decoder.md)<`A`> Decoder that ignores the input json and always fails with `errorMessage`. **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | errorMessage | `string` | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` intersection ▸ **intersection**A,B(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*): [Decoder](_decoder_.decoder.md)< `A` & `B`> ▸ **intersection**A,B,C(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*): [Decoder](_decoder_.decoder.md)< `A` & `B` & `C`> ▸ **intersection**A,B,C,D(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*): [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D`> ▸ **intersection**A,B,C,D,E(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*): [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E`> ▸ **intersection**A,B,C,D,E,F(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*, fd: *[Decoder](_decoder_.decoder.md)<`F`>*): [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F`> ▸ **intersection**A,B,C,D,E,F,G(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*, fd: *[Decoder](_decoder_.decoder.md)<`F`>*, gd: *[Decoder](_decoder_.decoder.md)<`G`>*): [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F` & `G`> ▸ **intersection**A,B,C,D,E,F,G,H(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*, fd: *[Decoder](_decoder_.decoder.md)<`F`>*, gd: *[Decoder](_decoder_.decoder.md)<`G`>*, hd: *[Decoder](_decoder_.decoder.md)<`H`>*): [Decoder](_decoder_.decoder.md)< `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 = object({name: string(), maxLegs: number()}); const catDecoder: Decoder = intersection(petDecoder, object({evil: boolean()})); ``` **Type parameters:** #### A #### B **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B`> **Type parameters:** #### A #### B #### C **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C`> **Type parameters:** #### A #### B #### C #### D **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D`> **Type parameters:** #### A #### B #### C #### D #### E **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E`> **Type parameters:** #### A #### B #### C #### D #### E #### F **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | | fd | [Decoder](_decoder_.decoder.md)<`F`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F`> **Type parameters:** #### A #### B #### C #### D #### E #### F #### G **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | | fd | [Decoder](_decoder_.decoder.md)<`F`> | | gd | [Decoder](_decoder_.decoder.md)<`G`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F` & `G`> **Type parameters:** #### A #### B #### C #### D #### E #### F #### G #### H **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | | fd | [Decoder](_decoder_.decoder.md)<`F`> | | gd | [Decoder](_decoder_.decoder.md)<`G`> | | hd | [Decoder](_decoder_.decoder.md)<`H`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F` & `G` & `H`> ___ ### `` lazy ▸ **lazy**A(mkDecoder: *`function`*): [Decoder](_decoder_.decoder.md)<`A`> 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 = object({ msg: string(), replies: lazy(() => array(decoder)) }); ``` **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | mkDecoder | `function` | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` number ▸ **number**(): [Decoder](_decoder_.decoder.md)<`number`> Decoder primitive that validates numbers, and fails on all other input. **Returns:** [Decoder](_decoder_.decoder.md)<`number`> ___ ### `` object ▸ **object**(): [Decoder](_decoder_.decoder.md)<`Record`<`string`, `unknown`>> ▸ **object**A(decoders: *[DecoderObject](../modules/_decoder_.md#decoderobject)<`A`>*): [Decoder](_decoder_.decoder.md)<`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']} ``` **Returns:** [Decoder](_decoder_.decoder.md)<`Record`<`string`, `unknown`>> **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | decoders | [DecoderObject](../modules/_decoder_.md#decoderobject)<`A`> | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` oneOf ▸ **oneOf**A(...decoders: *[Decoder](_decoder_.decoder.md)<`A`>[]*): [Decoder](_decoder_.decoder.md)<`A`> 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')) ``` **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | `Rest` decoders | [Decoder](_decoder_.decoder.md)<`A`>[] | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` optional ▸ **optional**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)< `undefined` | `A`> 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 = object({ id: number(), isOwner: optional(boolean()) }); ``` **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | **Returns:** [Decoder](_decoder_.decoder.md)< `undefined` | `A`> ___ ### `` string ▸ **string**(): [Decoder](_decoder_.decoder.md)<`string`> Decoder primitive that validates strings, and fails on all other input. **Returns:** [Decoder](_decoder_.decoder.md)<`string`> ___ ### `` succeed ▸ **succeed**A(fixedValue: *`A`*): [Decoder](_decoder_.decoder.md)<`A`> Decoder that ignores the input json and always succeeds with `fixedValue`. **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | fixedValue | `A` | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` tuple ▸ **tuple**A(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>]*): [Decoder](_decoder_.decoder.md)<[`A`]> ▸ **tuple**A,B(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`]> ▸ **tuple**A,B,C(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`]> ▸ **tuple**A,B,C,D(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`]> ▸ **tuple**A,B,C,D,E(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`]> ▸ **tuple**A,B,C,D,E,F(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>, [Decoder](_decoder_.decoder.md)<`F`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`]> ▸ **tuple**A,B,C,D,E,F,G(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>, [Decoder](_decoder_.decoder.md)<`F`>, [Decoder](_decoder_.decoder.md)<`G`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`, `G`]> ▸ **tuple**A,B,C,D,E,F,G,H(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>, [Decoder](_decoder_.decoder.md)<`F`>, [Decoder](_decoder_.decoder.md)<`G`>, [Decoder](_decoder_.decoder.md)<`H`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`, `G`, `H`]> 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']} ``` **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`]> **Type parameters:** #### A #### B **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`]> **Type parameters:** #### A #### B #### C **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`]> **Type parameters:** #### A #### B #### C #### D **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`]> **Type parameters:** #### A #### B #### C #### D #### E **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`]> **Type parameters:** #### A #### B #### C #### D #### E #### F **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>, [Decoder](_decoder_.decoder.md)<`F`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`]> **Type parameters:** #### A #### B #### C #### D #### E #### F #### G **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>, [Decoder](_decoder_.decoder.md)<`F`>, [Decoder](_decoder_.decoder.md)<`G`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`, `G`]> **Type parameters:** #### A #### B #### C #### D #### E #### F #### G #### H **Parameters:** | Param | Type | | ------ | ------ | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>, [Decoder](_decoder_.decoder.md)<`E`>, [Decoder](_decoder_.decoder.md)<`F`>, [Decoder](_decoder_.decoder.md)<`G`>, [Decoder](_decoder_.decoder.md)<`H`>] | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`, `G`, `H`]> ___ ### `` union ▸ **union**A,B(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*): [Decoder](_decoder_.decoder.md)< `A` | `B`> ▸ **union**A,B,C(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*): [Decoder](_decoder_.decoder.md)< `A` | `B` | `C`> ▸ **union**A,B,C,D(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*): [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D`> ▸ **union**A,B,C,D,E(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*): [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E`> ▸ **union**A,B,C,D,E,F(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*, fd: *[Decoder](_decoder_.decoder.md)<`F`>*): [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F`> ▸ **union**A,B,C,D,E,F,G(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*, fd: *[Decoder](_decoder_.decoder.md)<`F`>*, gd: *[Decoder](_decoder_.decoder.md)<`G`>*): [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F` | `G`> ▸ **union**A,B,C,D,E,F,G,H(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*, cd: *[Decoder](_decoder_.decoder.md)<`C`>*, dd: *[Decoder](_decoder_.decoder.md)<`D`>*, ed: *[Decoder](_decoder_.decoder.md)<`E`>*, fd: *[Decoder](_decoder_.decoder.md)<`F`>*, gd: *[Decoder](_decoder_.decoder.md)<`G`>*, hd: *[Decoder](_decoder_.decoder.md)<`H`>*): [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F` | `G` | `H`> 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 = union(object({a: string()}), object({b: number()})); const oneOfDecoder: Decoder = oneOf(object({a: string()}), object({b: number()})); ``` **Type parameters:** #### A #### B **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B`> **Type parameters:** #### A #### B #### C **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C`> **Type parameters:** #### A #### B #### C #### D **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D`> **Type parameters:** #### A #### B #### C #### D #### E **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E`> **Type parameters:** #### A #### B #### C #### D #### E #### F **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | | fd | [Decoder](_decoder_.decoder.md)<`F`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F`> **Type parameters:** #### A #### B #### C #### D #### E #### F #### G **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | | fd | [Decoder](_decoder_.decoder.md)<`F`> | | gd | [Decoder](_decoder_.decoder.md)<`G`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F` | `G`> **Type parameters:** #### A #### B #### C #### D #### E #### F #### G #### H **Parameters:** | Param | Type | | ------ | ------ | | ad | [Decoder](_decoder_.decoder.md)<`A`> | | bd | [Decoder](_decoder_.decoder.md)<`B`> | | cd | [Decoder](_decoder_.decoder.md)<`C`> | | dd | [Decoder](_decoder_.decoder.md)<`D`> | | ed | [Decoder](_decoder_.decoder.md)<`E`> | | fd | [Decoder](_decoder_.decoder.md)<`F`> | | gd | [Decoder](_decoder_.decoder.md)<`G`> | | hd | [Decoder](_decoder_.decoder.md)<`H`> | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F` | `G` | `H`> ___ ### `` unknownJson ▸ **unknownJson**(): [Decoder](_decoder_.decoder.md)<`unknown`> Decoder identity function which always succeeds and types the result as `unknown`. **Returns:** [Decoder](_decoder_.decoder.md)<`unknown`> ___ ### `` valueAt ▸ **valueAt**A(paths: *( `string` | `number`)[]*, decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`A`> 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} ``` **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | paths | ( `string` | `number`)[] | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ### `` withDefault ▸ **withDefault**A(defaultValue: *`A`*, decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`A`> Decoder that always succeeds with either the decoded value, or a fallback default value. **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | defaultValue | `A` | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> ___ ================================================ FILE: docs/interfaces/_decoder_.decodererror.md ================================================ [@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) > [DecoderError](../interfaces/_decoder_.decodererror.md) # Interface: DecoderError 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. ## Hierarchy **DecoderError** ## Index ### Properties * [at](_decoder_.decodererror.md#at) * [input](_decoder_.decodererror.md#input) * [kind](_decoder_.decodererror.md#kind) * [message](_decoder_.decodererror.md#message) --- ## Properties ### at **● at**: *`string`* ___ ### input **● input**: *`unknown`* ___ ### kind **● kind**: *"DecoderError"* ___ ### message **● message**: *`string`* ___ ================================================ FILE: docs/interfaces/_result_.err.md ================================================ [@mojotech/json-type-validation](../README.md) > ["result"](../modules/_result_.md) > [Err](../interfaces/_result_.err.md) # Interface: Err The error type variant for `Result`. Denotes that some error occurred before the result was computed. ## Type parameters #### E ## Hierarchy **Err** ## Index ### Properties * [error](_result_.err.md#error) * [ok](_result_.err.md#ok) --- ## Properties ### error **● error**: *`E`* ___ ### ok **● ok**: *`false`* ___ ================================================ FILE: docs/interfaces/_result_.ok.md ================================================ [@mojotech/json-type-validation](../README.md) > ["result"](../modules/_result_.md) > [Ok](../interfaces/_result_.ok.md) # Interface: Ok The success type variant for `Result`. Denotes that a result value was computed with no errors. ## Type parameters #### V ## Hierarchy **Ok** ## Index ### Properties * [ok](_result_.ok.md#ok) * [result](_result_.ok.md#result) --- ## Properties ### ok **● ok**: *`true`* ___ ### result **● result**: *`V`* ___ ================================================ FILE: docs/modules/_combinators_.md ================================================ [@mojotech/json-type-validation](../README.md) > ["combinators"](../modules/_combinators_.md) # External module: "combinators" ## Index ### Variables * [anyJson](_combinators_.md#anyjson) * [array](_combinators_.md#array) * [boolean](_combinators_.md#boolean) * [constant](_combinators_.md#constant) * [dict](_combinators_.md#dict) * [fail](_combinators_.md#fail) * [intersection](_combinators_.md#intersection) * [lazy](_combinators_.md#lazy) * [number](_combinators_.md#number) * [object](_combinators_.md#object) * [oneOf](_combinators_.md#oneof) * [optional](_combinators_.md#optional) * [string](_combinators_.md#string) * [succeed](_combinators_.md#succeed) * [tuple](_combinators_.md#tuple) * [union](_combinators_.md#union) * [unknownJson](_combinators_.md#unknownjson) * [valueAt](_combinators_.md#valueat) * [withDefault](_combinators_.md#withdefault) --- ## Variables ### `` anyJson **● anyJson**: *[anyJson]()* = Decoder.anyJson See `Decoder.anyJson` ___ ### `` array **● array**: *[array](../classes/_decoder_.decoder.md#array)* = Decoder.array See `Decoder.array` ___ ### `` boolean **● boolean**: *[boolean](../classes/_decoder_.decoder.md#boolean)* = Decoder.boolean See `Decoder.boolean` ___ ### `` constant **● constant**: *[constant](../classes/_decoder_.decoder.md#constant)* = Decoder.constant See `Decoder.constant` ___ ### `` dict **● dict**: *[dict]()* = Decoder.dict See `Decoder.dict` ___ ### `` fail **● fail**: *[fail]()* = Decoder.fail See `Decoder.fail` ___ ### `` intersection **● intersection**: *[intersection](../classes/_decoder_.decoder.md#intersection)* = Decoder.intersection See `Decoder.intersection` ___ ### `` lazy **● lazy**: *[lazy]()* = Decoder.lazy See `Decoder.lazy` ___ ### `` number **● number**: *[number](../classes/_decoder_.decoder.md#number)* = Decoder.number See `Decoder.number` ___ ### `` object **● object**: *[object](../classes/_decoder_.decoder.md#object)* = Decoder.object See `Decoder.object` ___ ### `` oneOf **● oneOf**: *[oneOf]()* = Decoder.oneOf See `Decoder.oneOf` ___ ### `` optional **● optional**: *[optional]()* = Decoder.optional See `Decoder.optional` ___ ### `` string **● string**: *[string](../classes/_decoder_.decoder.md#string)* = Decoder.string See `Decoder.string` ___ ### `` succeed **● succeed**: *[succeed]()* = Decoder.succeed See `Decoder.succeed` ___ ### `` tuple **● tuple**: *[tuple](../classes/_decoder_.decoder.md#tuple)* = Decoder.tuple See `Decoder.tuple` ___ ### `` union **● union**: *[union](../classes/_decoder_.decoder.md#union)* = Decoder.union See `Decoder.union` ___ ### `` unknownJson **● unknownJson**: *`function`* = Decoder.unknownJson See `Decoder.unknownJson` #### Type declaration ▸(): [Decoder](../classes/_decoder_.decoder.md)<`unknown`> **Returns:** [Decoder](../classes/_decoder_.decoder.md)<`unknown`> ___ ### `` valueAt **● valueAt**: *[valueAt]()* = Decoder.valueAt See `Decoder.valueAt` ___ ### `` withDefault **● withDefault**: *[withDefault]()* = Decoder.withDefault See `Decoder.withDefault` ___ ================================================ FILE: docs/modules/_decoder_.md ================================================ [@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) # External module: "decoder" ## Index ### Classes * [Decoder](../classes/_decoder_.decoder.md) ### Interfaces * [DecoderError](../interfaces/_decoder_.decodererror.md) ### Type aliases * [DecoderObject](_decoder_.md#decoderobject) ### Functions * [isDecoderError](_decoder_.md#isdecodererror) --- ## Type aliases ### DecoderObject **ΤDecoderObject**: *`object`* Defines a mapped type over an interface `A`. `DecoderObject` 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 = { a: boolean(), b: string() } ``` #### Type declaration ___ ## Functions ### `` isDecoderError ▸ **isDecoderError**(a: *`any`*): `boolean` 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. **Parameters:** | Param | Type | | ------ | ------ | | a | `any` | **Returns:** `boolean` ___ ================================================ FILE: docs/modules/_index_.md ================================================ [@mojotech/json-type-validation](../README.md) > ["index"](../modules/_index_.md) # External module: "index" ## Index --- ================================================ FILE: docs/modules/_result_.md ================================================ [@mojotech/json-type-validation](../README.md) > ["result"](../modules/_result_.md) # External module: "result" ## Index ### Interfaces * [Err](../interfaces/_result_.err.md) * [Ok](../interfaces/_result_.ok.md) ### Type aliases * [Result](_result_.md#result) ### Functions * [andThen](_result_.md#andthen) * [asPromise](_result_.md#aspromise) * [err](_result_.md#err-1) * [isErr](_result_.md#iserr) * [isOk](_result_.md#isok) * [map](_result_.md#map) * [map2](_result_.md#map2) * [mapError](_result_.md#maperror) * [ok](_result_.md#ok-1) * [successes](_result_.md#successes) * [withDefault](_result_.md#withdefault) * [withException](_result_.md#withexception) --- ## Type aliases ### Result **ΤResult**: * [Ok](../interfaces/_result_.ok.md)<`V`> | [Err](../interfaces/_result_.err.md)<`E`> * The result of a computation that may fail. The decoding function `Decoder.run` returns a `Result`. The value of a `Result` is either `Ok` if the computation succeeded, or `Err` if there was some failure in the process. ___ ## Functions ### `` andThen ▸ **andThen**A,B,E(f: *`function`*, r: *[Result](_result_.md#result)<`A`, `E`>*): [Result](_result_.md#result)<`B`, `E`> Chain together a sequence of computations that may fail, similar to a `Promise`. If the first computation fails then the error will propagate through. If it succeeds, then `f` will be applied to the value, returning a new `Result`. **Type parameters:** #### A #### B #### E **Parameters:** | Param | Type | | ------ | ------ | | f | `function` | | r | [Result](_result_.md#result)<`A`, `E`> | **Returns:** [Result](_result_.md#result)<`B`, `E`> ___ ### `` asPromise ▸ **asPromise**V(r: *[Result](_result_.md#result)<`V`, `any`>*): `Promise`<`V`> Create a `Promise` that either resolves with the result of `Ok` or rejects with the error of `Err`. **Type parameters:** #### V **Parameters:** | Param | Type | | ------ | ------ | | r | [Result](_result_.md#result)<`V`, `any`> | **Returns:** `Promise`<`V`> ___ ### `` err ▸ **err**E(error: *`E`*): [Err](../interfaces/_result_.err.md)<`E`> Wraps errors in an `Err` type. Example: `err('on fire') // => {ok: false, error: 'on fire'}` **Type parameters:** #### E **Parameters:** | Param | Type | | ------ | ------ | | error | `E` | **Returns:** [Err](../interfaces/_result_.err.md)<`E`> ___ ### `` isErr ▸ **isErr**E(r: *[Result](_result_.md#result)<`any`, `E`>*): `boolean` Typeguard for `Err`. **Type parameters:** #### E **Parameters:** | Param | Type | | ------ | ------ | | r | [Result](_result_.md#result)<`any`, `E`> | **Returns:** `boolean` ___ ### `` isOk ▸ **isOk**V(r: *[Result](_result_.md#result)<`V`, `any`>*): `boolean` Typeguard for `Ok`. **Type parameters:** #### V **Parameters:** | Param | Type | | ------ | ------ | | r | [Result](_result_.md#result)<`V`, `any`> | **Returns:** `boolean` ___ ### `` map ▸ **map**A,B,E(f: *`function`*, r: *[Result](_result_.md#result)<`A`, `E`>*): [Result](_result_.md#result)<`B`, `E`> Apply `f` to the result of an `Ok`, or pass the error through. **Type parameters:** #### A #### B #### E **Parameters:** | Param | Type | | ------ | ------ | | f | `function` | | r | [Result](_result_.md#result)<`A`, `E`> | **Returns:** [Result](_result_.md#result)<`B`, `E`> ___ ### `` map2 ▸ **map2**A,B,C,E(f: *`function`*, ar: *[Result](_result_.md#result)<`A`, `E`>*, br: *[Result](_result_.md#result)<`B`, `E`>*): [Result](_result_.md#result)<`C`, `E`> Apply `f` to the result of two `Ok`s, or pass an error through. If both `Result`s are errors then the first one is returned. **Type parameters:** #### A #### B #### C #### E **Parameters:** | Param | Type | | ------ | ------ | | f | `function` | | ar | [Result](_result_.md#result)<`A`, `E`> | | br | [Result](_result_.md#result)<`B`, `E`> | **Returns:** [Result](_result_.md#result)<`C`, `E`> ___ ### `` mapError ▸ **mapError**V,A,B(f: *`function`*, r: *[Result](_result_.md#result)<`V`, `A`>*): [Result](_result_.md#result)<`V`, `B`> Apply `f` to the error of an `Err`, or pass the success through. **Type parameters:** #### V #### A #### B **Parameters:** | Param | Type | | ------ | ------ | | f | `function` | | r | [Result](_result_.md#result)<`V`, `A`> | **Returns:** [Result](_result_.md#result)<`V`, `B`> ___ ### `` ok ▸ **ok**V(result: *`V`*): [Ok](../interfaces/_result_.ok.md)<`V`> Wraps values in an `Ok` type. Example: `ok(5) // => {ok: true, result: 5}` **Type parameters:** #### V **Parameters:** | Param | Type | | ------ | ------ | | result | `V` | **Returns:** [Ok](../interfaces/_result_.ok.md)<`V`> ___ ### `` successes ▸ **successes**A(results: *[Result](_result_.md#result)<`A`, `any`>[]*): `A`[] Given an array of `Result`s, return the successful values. **Type parameters:** #### A **Parameters:** | Param | Type | | ------ | ------ | | results | [Result](_result_.md#result)<`A`, `any`>[] | **Returns:** `A`[] ___ ### `` withDefault ▸ **withDefault**V(defaultValue: *`V`*, r: *[Result](_result_.md#result)<`V`, `any`>*): `V` Unwraps a `Result` and returns either the result of an `Ok`, or `defaultValue`. Example: ``` Result.withDefault(5, number().run(json)) ``` It would be nice if `Decoder` had an instance method that mirrored this function. Such a method would look something like this: ``` class Decoder { runWithDefault = (defaultValue: A, json: any): A => Result.withDefault(defaultValue, this.run(json)); } number().runWithDefault(5, json) ``` Unfortunately, the type of `defaultValue: A` on the method causes issues with type inference on the `object` decoder in some situations. While these inference issues can be solved by providing the optional type argument for `object`s, the extra trouble and confusion doesn't seem worth it. **Type parameters:** #### V **Parameters:** | Param | Type | | ------ | ------ | | defaultValue | `V` | | r | [Result](_result_.md#result)<`V`, `any`> | **Returns:** `V` ___ ### `` withException ▸ **withException**V(r: *[Result](_result_.md#result)<`V`, `any`>*): `V` Return the successful result, or throw an error. **Type parameters:** #### V **Parameters:** | Param | Type | | ------ | ------ | | r | [Result](_result_.md#result)<`V`, `any`> | **Returns:** `V` ___ ================================================ FILE: nix-files/default.nix ================================================ { nixpkgsFn ? import ./nixpkgs.nix , system ? null }: let nixpkgs = nixpkgsFn ({ # extra config goes here } // ( if system == null then {} else { inherit system; } )); in nixpkgs.stdenv.mkDerivation { name = "json-type-validator"; buildInputs = with nixpkgs; [ nodejs yarn git ]; src = "./"; builder = builtins.toFile "builder.sh" '' echo "Use this derivation with nix-shell only" exit 1 ''; shellHook = '' # Get to the source directory cd $src # Install any new dependencies yarn # Add node_modules to path export PATH=$src/node_modules/bin:$PATH ''; } ================================================ FILE: nix-files/nixpkgs.nix ================================================ let source = '' { "owner": "NixOS", "repo": "nixpkgs-channels", "rev": "aebdc892d6aa6834a083fb8b56c43578712b0dab", "sha256": "1bcpjc7f1ff5k7vf5rwwb7g7m4j238hi4ssnx7xqglr7hj4ms0cz" } ''; in import ((import {}).fetchFromGitHub (builtins.fromJSON (source))) ================================================ FILE: nix-files/shell.nix ================================================ { nixpkgsFn ? import ./nixpkgs.nix , package ? ./default.nix }: (import package { inherit nixpkgsFn; }) ================================================ FILE: package.json ================================================ { "name": "@mojotech/json-type-validation", "version": "3.1.0", "description": "runtime type checking and validation of untyped JSON data", "keywords": [ "TypeScript", "JSON" ], "main": "dist/index.js", "module": "dist/index.es5.js", "typings": "dist/types/index.d.ts", "files": [ "dist" ], "author": "Elias Mulhall ", "repository": { "type": "git", "url": "https://github.com/mojotech/json-type-validation" }, "bugs": { "url": "https://github.com/mojotech/json-type-validation/issues" }, "homepage": "https://github.com/mojotech/json-type-validation", "license": "MIT", "engines": { "node": ">=6.0.0" }, "scripts": { "lint": "tslint -t codeFrame --project tsconfig-test.json", "prebuild": "rimraf dist", "build": "tsc --module commonjs --outDir dist/lib && rollup -c rollup.config.ts && typedoc --out docs --target es6 --theme markdown --readme DOCS.md --mdHideSources --mode modules --excludeNotExported src", "start": "rollup -c rollup.config.ts -w", "test": "jest", "test:watch": "jest --watch", "test:prod": "npm run lint && npm run test -- --coverage --no-cache", "typecheck": "tsc --lib es2015 --noEmit --strict test/**.ts", "typecheck:watch": "tsc -w --lib es2015 --noEmit --strict test/**.ts" }, "jest": { "transform": { ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" }, "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", "moduleFileExtensions": [ "ts", "tsx", "js" ], "coveragePathIgnorePatterns": [ "/node_modules/", "/test/" ], "coverageThreshold": { "global": { "branches": 90, "functions": 95, "lines": 95, "statements": 95 } }, "collectCoverage": true }, "devDependencies": { "@types/jest": "^22.2.3", "@types/node": "^10.5.3", "@types/lodash": "^4.5.0", "colors": "^1.1.2", "cross-env": "^5.0.1", "jest": "^22.0.2", "prettier": "^1.4.4", "rimraf": "^2.6.1", "rollup": "^0.53.0", "rollup-plugin-commonjs": "^8.0.2", "rollup-plugin-node-resolve": "^3.0.0", "rollup-plugin-sourcemaps": "^0.4.2", "rollup-plugin-typescript2": "^0.9.0", "ts-jest": "^22.0.0", "ts-node": "^4.1.0", "tslint": "^5.11.0", "tslint-config-prettier": "^1.14.0", "tslint-config-standard": "^7.1.0", "typedoc": "^0.13.0", "typedoc-plugin-markdown": "^1.0.13", "typescript": "~3.1.0" }, "dependencies": { "lodash": "^4.5.0" } } ================================================ FILE: rollup.config.ts ================================================ import resolve from 'rollup-plugin-node-resolve'; import sourceMaps from 'rollup-plugin-sourcemaps'; import typescript from 'rollup-plugin-typescript2'; const pkg = require('./package.json'); export default { input: `src/index.ts`, output: [ { file: pkg.main, name: 'index', format: 'cjs', sourcemap: true }, { file: pkg.module, format: 'es', sourcemap: true } ], external: id => { return id.includes('/node_modules/'); }, watch: { include: 'src/**' }, plugins: [ // Compile TypeScript files typescript({ useTsconfigDeclarationDir: true }), resolve(), // Resolve source maps to the original source sourceMaps() ] }; ================================================ FILE: src/combinators.ts ================================================ import {Decoder} from './decoder'; /* tslint:disable:variable-name */ /** See `Decoder.string` */ export const string = Decoder.string; /** See `Decoder.number` */ export const number = Decoder.number; /** See `Decoder.boolean` */ export const boolean = Decoder.boolean; /** See `Decoder.anyJson` */ export const anyJson = Decoder.anyJson; /** See `Decoder.unknownJson` */ export const unknownJson: () => Decoder = Decoder.unknownJson; /** See `Decoder.constant` */ export const constant = Decoder.constant; /** See `Decoder.object` */ export const object = Decoder.object; /** See `Decoder.array` */ export const array = Decoder.array; /** See `Decoder.tuple` */ export const tuple = Decoder.tuple; /** See `Decoder.dict` */ export const dict = Decoder.dict; /** See `Decoder.optional` */ export const optional = Decoder.optional; /** See `Decoder.oneOf` */ export const oneOf = Decoder.oneOf; /** See `Decoder.union` */ export const union = Decoder.union; /** See `Decoder.intersection` */ export const intersection = Decoder.intersection; /** See `Decoder.withDefault` */ export const withDefault = Decoder.withDefault; /** See `Decoder.valueAt` */ export const valueAt = Decoder.valueAt; /** See `Decoder.succeed` */ export const succeed = Decoder.succeed; /** See `Decoder.fail` */ export const fail = Decoder.fail; /** See `Decoder.lazy` */ export const lazy = Decoder.lazy; ================================================ FILE: src/decoder.ts ================================================ import * as Result from './result'; import isEqual from "lodash/isEqual" /** * 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; } /** * Alias for the result of the `Decoder.run` method. On success returns `Ok` * with the decoded value of type `A`, on failure returns `Err` containing a * `DecoderError`. */ type RunResult = Result.Result; /** * Alias for the result of the internal `Decoder.decode` method. Since `decode` * is a private function it returns a partial decoder error on failure, which * will be completed and polished when handed off to the `run` method. */ type DecodeResult = Result.Result>; /** * Defines a mapped type over an interface `A`. `DecoderObject` 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 = { * a: boolean(), * b: string() * } * ``` */ export type DecoderObject = {[t in keyof A]: Decoder}; /** * 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 const isDecoderError = (a: any): a is DecoderError => a.kind === 'DecoderError' && typeof a.at === 'string' && typeof a.message === 'string'; /* * Helpers */ const isJsonArray = (json: any): json is unknown[] => Array.isArray(json); const isJsonObject = (json: any): json is Record => typeof json === 'object' && json !== null && !isJsonArray(json); const typeString = (json: unknown): string => { switch (typeof json) { case 'string': return 'a string'; case 'number': return 'a number'; case 'boolean': return 'a boolean'; case 'undefined': return 'undefined'; case 'object': if (json instanceof Array) { return 'an array'; } else if (json === null) { return 'null'; } else { return 'an object'; } default: return JSON.stringify(json); } }; const expectedGot = (expected: string, got: unknown) => `expected ${expected}, got ${typeString(got)}`; const printPath = (paths: (string | number)[]): string => paths.map(path => (typeof path === 'string' ? `.${path}` : `[${path}]`)).join(''); const prependAt = (newAt: string, {at, ...rest}: Partial): Partial => ({ at: newAt + (at || ''), ...rest }); /** * Decoders transform json objects with unknown structure into known and * verified forms. You can create objects of type `Decoder` 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`. 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 class Decoder { /** * 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` 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` * 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(private decode: (json: unknown) => DecodeResult) {} /** * Decoder primitive that validates strings, and fails on all other input. */ static string(): Decoder { return new Decoder( (json: unknown) => typeof json === 'string' ? Result.ok(json) : Result.err({message: expectedGot('a string', json)}) ); } /** * Decoder primitive that validates numbers, and fails on all other input. */ static number(): Decoder { return new Decoder( (json: unknown) => typeof json === 'number' ? Result.ok(json) : Result.err({message: expectedGot('a number', json)}) ); } /** * Decoder primitive that validates booleans, and fails on all other input. */ static boolean(): Decoder { return new Decoder( (json: unknown) => typeof json === 'boolean' ? Result.ok(json) : Result.err({message: expectedGot('a boolean', json)}) ); } /** * 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 = object({ * name: string(), * complexUserData: anyJson() * }); * ``` */ static anyJson = (): Decoder => new Decoder((json: any) => Result.ok(json)); /** * Decoder identity function which always succeeds and types the result as * `unknown`. */ static unknownJson = (): Decoder => new Decoder((json: unknown) => Result.ok(json)); /** * Decoder primitive that only matches on exact values. * * For primitive values and shallow structures of primitive values `constant` * will infer an exact literal type: * ``` * | Decoder | Type | * | ---------------------------- | ------------------------------| * | constant(true) | Decoder | * | constant(false) | Decoder | * | constant(null) | Decoder | * | constant(undefined) | Decoder | * | constant('alaska') | Decoder<'alaska'> | * | constant(50) | Decoder<50> | * | constant([1,2,3]) | Decoder<[1,2,3]> | * | constant({x: 't'}) | Decoder<{x: 't'}> | * ``` * * Inference breaks on nested structures, which require an annotation to get * the literal type: * ``` * | Decoder | Type | * | -----------------------------|-------------------------------| * | constant([1,[2]]) | Decoder<(number|number[])[]> | * | constant<[1,[2]]>([1,[2]]) | Decoder<[1,[2]]> | * | constant({x: [1]}) | Decoder<{x: number[]}> | * | constant<{x: [1]}>({x: [1]}) | Decoder<{x: [1]}> | * ``` */ static constant(value: T): Decoder; static constant(value: U): Decoder; static constant>(value: U): Decoder; static constant(value: T): Decoder; static constant(value: any) { return new Decoder( (json: unknown) => isEqual(json, value) ? Result.ok(value) : Result.err({message: `expected ${JSON.stringify(value)}, got ${JSON.stringify(json)}`}) ); } /** * 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>; static object(decoders: DecoderObject): Decoder; static object(decoders?: DecoderObject) { return new Decoder((json: unknown) => { if (isJsonObject(json) && decoders) { let obj: any = {}; for (const key in decoders) { if (decoders.hasOwnProperty(key)) { const r = decoders[key].decode(json[key]); if (r.ok === true) { // tslint:disable-next-line:strict-type-predicates if (r.result !== undefined) { obj[key] = r.result; } } else if (json[key] === undefined) { return Result.err({message: `the key '${key}' is required but was not present`}); } else { return Result.err(prependAt(`.${key}`, r.error)); } } } return Result.ok(obj); } else if (isJsonObject(json)) { return Result.ok(json); } else { return Result.err({message: expectedGot('an object', json)}); } }); } /** * 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; static array(decoder: Decoder): Decoder; static array(decoder?: Decoder) { return new Decoder(json => { if (isJsonArray(json) && decoder) { const decodeValue = (v: unknown, i: number): DecodeResult => Result.mapError(err => prependAt(`[${i}]`, err), decoder.decode(v)); return json.reduce( (acc: DecodeResult, v: unknown, i: number) => Result.map2((arr, result) => [...arr, result], acc, decodeValue(v, i)), Result.ok([]) ); } else if (isJsonArray(json)) { return Result.ok(json); } else { return Result.err({message: expectedGot('an array', json)}); } }); } /** * 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(decoder: [Decoder]): Decoder<[A]>; static tuple(decoder: [Decoder, Decoder]): Decoder<[A, B]>; static tuple(decoder: [Decoder, Decoder, Decoder]): Decoder<[A, B, C]>; static tuple(decoder: [Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D]>; // prettier-ignore static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E]>; // prettier-ignore static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E, F]>; // prettier-ignore static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E, F, G]>; // prettier-ignore static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E, F, G, H]>; // prettier-ignore static tuple(decoders: Decoder[]) { return new Decoder((json: unknown) => { if (isJsonArray(json)) { if (json.length !== decoders.length) { return Result.err({ message: `expected a tuple of length ${decoders.length}, got one of length ${ json.length }` }); } const result = []; for (let i: number = 0; i < decoders.length; i++) { const nth = decoders[i].decode(json[i]); if (nth.ok) { result[i] = nth.result; } else { return Result.err(prependAt(`[${i}]`, nth.error)); } } return Result.ok(result); } else { return Result.err({message: expectedGot(`a tuple of length ${decoders.length}`, json)}); } }); } /** * 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 = (decoder: Decoder): Decoder> => new Decoder(json => { if (isJsonObject(json)) { let obj: Record = {}; for (const key in json) { if (json.hasOwnProperty(key)) { const r = decoder.decode(json[key]); if (r.ok === true) { obj[key] = r.result; } else { return Result.err(prependAt(`.${key}`, r.error)); } } } return Result.ok(obj); } else { return Result.err({message: expectedGot('an object', json)}); } }); /** * 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 = object({ * id: number(), * isOwner: optional(boolean()) * }); * ``` */ static optional = (decoder: Decoder): Decoder => new Decoder( (json: unknown) => (json === undefined ? Result.ok(undefined) : decoder.decode(json)) ); /** * 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 = (...decoders: Decoder[]): Decoder => new Decoder((json: unknown) => { const errors: Partial[] = []; for (let i: number = 0; i < decoders.length; i++) { const r = decoders[i].decode(json); if (r.ok === true) { return r; } else { errors[i] = r.error; } } const errorsList = errors .map(error => `at error${error.at || ''}: ${error.message}`) .join('", "'); return Result.err({ message: `expected a value matching one of the decoders, got the errors ["${errorsList}"]` }); }); /** * 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 = union(object({a: string()}), object({b: number()})); * const oneOfDecoder: Decoder = oneOf(object({a: string()}), object({b: number()})); * ``` */ static union (ad: Decoder, bd: Decoder): Decoder; // prettier-ignore static union (ad: Decoder, bd: Decoder, cd: Decoder): Decoder; // prettier-ignore static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder): Decoder; // prettier-ignore static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder): Decoder; // prettier-ignore static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder): Decoder; // prettier-ignore static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder): Decoder; // prettier-ignore static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder, hd: Decoder): Decoder; // prettier-ignore static union(ad: Decoder, bd: Decoder, ...decoders: Decoder[]): Decoder { return Decoder.oneOf(ad, bd, ...decoders); } /** * 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 = object({name: string(), maxLegs: number()}); * const catDecoder: Decoder = intersection(petDecoder, object({evil: boolean()})); * ``` */ static intersection (ad: Decoder, bd: Decoder): Decoder; // prettier-ignore static intersection (ad: Decoder, bd: Decoder, cd: Decoder): Decoder; // prettier-ignore static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder): Decoder; // prettier-ignore static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder): Decoder; // prettier-ignore static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder): Decoder; // prettier-ignore static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder): Decoder; // prettier-ignore static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder, hd: Decoder): Decoder; // prettier-ignore static intersection(ad: Decoder, bd: Decoder, ...ds: Decoder[]): Decoder { return new Decoder((json: unknown) => [ad, bd, ...ds].reduce( (acc: DecodeResult, decoder) => Result.map2(Object.assign, acc, decoder.decode(json)), Result.ok({}) ) ); } /** * Decoder that always succeeds with either the decoded value, or a fallback * default value. */ static withDefault = (defaultValue: A, decoder: Decoder): Decoder => new Decoder((json: unknown) => Result.ok(Result.withDefault(defaultValue, decoder.decode(json))) ); /** * 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 = (paths: (string | number)[], decoder: Decoder): Decoder => new Decoder((json: unknown) => { let jsonAtPath: any = json; for (let i: number = 0; i < paths.length; i++) { if (jsonAtPath === undefined) { return Result.err({ at: printPath(paths.slice(0, i + 1)), message: 'path does not exist' }); } else if (typeof paths[i] === 'string' && !isJsonObject(jsonAtPath)) { return Result.err({ at: printPath(paths.slice(0, i + 1)), message: expectedGot('an object', jsonAtPath) }); } else if (typeof paths[i] === 'number' && !isJsonArray(jsonAtPath)) { return Result.err({ at: printPath(paths.slice(0, i + 1)), message: expectedGot('an array', jsonAtPath) }); } else { jsonAtPath = jsonAtPath[paths[i]]; } } return Result.mapError( error => jsonAtPath === undefined ? {at: printPath(paths), message: 'path does not exist'} : prependAt(printPath(paths), error), decoder.decode(jsonAtPath) ); }); /** * Decoder that ignores the input json and always succeeds with `fixedValue`. */ static succeed = (fixedValue: A): Decoder => new Decoder((json: unknown) => Result.ok(fixedValue)); /** * Decoder that ignores the input json and always fails with `errorMessage`. */ static fail = (errorMessage: string): Decoder => new Decoder((json: unknown) => Result.err({message: errorMessage})); /** * 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 = object({ * msg: string(), * replies: lazy(() => array(decoder)) * }); * ``` */ static lazy = (mkDecoder: () => Decoder): Decoder => new Decoder((json: unknown) => mkDecoder().decode(json)); /** * 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): RunResult => Result.mapError( error => ({ kind: 'DecoderError' as 'DecoderError', input: json, at: 'input' + (error.at || ''), message: error.message || '' }), this.decode(json) ); /** * Run the decoder as a `Promise`. */ runPromise = (json: unknown): Promise => Result.asPromise(this.run(json)); /** * Run the decoder and return the value on success, or throw an exception * with a formatted error string. */ runWithException = (json: unknown): A => Result.withException(this.run(json)); /** * 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 = (f: (value: A) => B): Decoder => new Decoder((json: unknown) => Result.map(f, this.decode(json))); /** * 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[] & { __nonEmptyArrayBrand__: void }; * * const nonEmptyArrayDecoder = (values: Decoder): Decoder> => * array(values).andThen(arr => * arr.length > 0 * ? succeed(createNonEmptyArray(arr)) * : fail(`expected a non-empty array, got an empty array`) * ); * ``` */ andThen = (f: (value: A) => Decoder): Decoder => new Decoder((json: unknown) => Result.andThen(value => f(value).decode(json), this.decode(json)) ); /** * 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().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 => this.andThen((value: A) => (test(value) ? Decoder.succeed(value) : Decoder.fail(errorMessage))); } ================================================ FILE: src/index.ts ================================================ import * as Result from './result'; export {Result}; export {Decoder, DecoderError, isDecoderError, DecoderObject} from './decoder'; export { string, number, boolean, anyJson, unknownJson, constant, object, array, tuple, dict, optional, oneOf, union, intersection, withDefault, valueAt, succeed, fail, lazy } from './combinators'; ================================================ FILE: src/result.ts ================================================ /** * The result of a computation that may fail. The decoding function * `Decoder.run` returns a `Result`. The value of a `Result` is either `Ok` if * the computation succeeded, or `Err` if there was some failure in the * process. */ export type Result = Ok | Err; /** * The success type variant for `Result`. Denotes that a result value was * computed with no errors. */ export interface Ok { ok: true; result: V; } /** * The error type variant for `Result`. Denotes that some error occurred before * the result was computed. */ export interface Err { ok: false; error: E; } /** * Wraps values in an `Ok` type. * * Example: `ok(5) // => {ok: true, result: 5}` */ export const ok = (result: V): Ok => ({ok: true, result: result}); /** * Typeguard for `Ok`. */ export const isOk = (r: Result): r is Ok => r.ok === true; /** * Wraps errors in an `Err` type. * * Example: `err('on fire') // => {ok: false, error: 'on fire'}` */ export const err = (error: E): Err => ({ok: false, error: error}); /** * Typeguard for `Err`. */ export const isErr = (r: Result): r is Err => r.ok === false; /** * Create a `Promise` that either resolves with the result of `Ok` or rejects * with the error of `Err`. */ export const asPromise = (r: Result): Promise => r.ok === true ? Promise.resolve(r.result) : Promise.reject(r.error); /** * Unwraps a `Result` and returns either the result of an `Ok`, or * `defaultValue`. * * Example: * ``` * Result.withDefault(5, number().run(json)) * ``` * * It would be nice if `Decoder` had an instance method that mirrored this * function. Such a method would look something like this: * ``` * class Decoder { * runWithDefault = (defaultValue: A, json: any): A => * Result.withDefault(defaultValue, this.run(json)); * } * * number().runWithDefault(5, json) * ``` * Unfortunately, the type of `defaultValue: A` on the method causes issues * with type inference on the `object` decoder in some situations. While these * inference issues can be solved by providing the optional type argument for * `object`s, the extra trouble and confusion doesn't seem worth it. */ export const withDefault = (defaultValue: V, r: Result): V => r.ok === true ? r.result : defaultValue; /** * Return the successful result, or throw an error. */ export const withException = (r: Result): V => { if (r.ok === true) { return r.result; } else { throw r.error; } }; /** * Given an array of `Result`s, return the successful values. */ export const successes = (results: Result[]): A[] => results.reduce((acc: A[], r: Result) => (r.ok === true ? acc.concat(r.result) : acc), []); /** * Apply `f` to the result of an `Ok`, or pass the error through. */ export const map = (f: (value: A) => B, r: Result): Result => r.ok === true ? ok(f(r.result)) : r; /** * Apply `f` to the result of two `Ok`s, or pass an error through. If both * `Result`s are errors then the first one is returned. */ export const map2 = (f: (av: A, bv: B) => C, ar: Result, br: Result): Result => ar.ok === false ? ar : br.ok === false ? br : ok(f(ar.result, br.result)); /** * Apply `f` to the error of an `Err`, or pass the success through. */ export const mapError = (f: (error: A) => B, r: Result): Result => r.ok === true ? r : err(f(r.error)); /** * Chain together a sequence of computations that may fail, similar to a * `Promise`. If the first computation fails then the error will propagate * through. If it succeeds, then `f` will be applied to the value, returning a * new `Result`. */ export const andThen = (f: (value: A) => Result, r: Result): Result => r.ok === true ? f(r.result) : r; ================================================ FILE: test/json-decode.test.ts ================================================ import { Decoder, Result, isDecoderError, string, number, boolean, anyJson, unknownJson, constant, object, array, dict, optional, oneOf, union, intersection, withDefault, valueAt, succeed, tuple, fail, lazy } from '../src/index'; describe('string', () => { const decoder = string(); it('succeeds when given a string', () => { expect(decoder.run('hey')).toEqual({ok: true, result: 'hey'}); }); it('fails when given a number', () => { expect(decoder.run(1)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a string, got a number'} }); }); it('fails when given null', () => { expect(decoder.run(null)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a string, got null'} }); }); it('fails when given a boolean', () => { expect(decoder.run(true)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a string, got a boolean'} }); }); }); describe('number', () => { const decoder = number(); it('succeeds when given a number', () => { expect(decoder.run(5)).toEqual({ok: true, result: 5}); }); it('fails when given a string', () => { expect(decoder.run('hey')).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a number, got a string'} }); }); it('fails when given boolean', () => { expect(decoder.run(true)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a number, got a boolean'} }); }); }); describe('boolean', () => { const decoder = boolean(); it('succeeds when given a boolean', () => { expect(decoder.run(true)).toEqual({ok: true, result: true}); }); it('fails when given a string', () => { expect(decoder.run('hey')).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a boolean, got a string'} }); }); it('fails when given a number', () => { expect(decoder.run(1)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a boolean, got a number'} }); }); }); describe('anyJson', () => { it('bypasses type validation', () => { // in a real use case this could be a deeply nested object type ComplexType = number; interface User { name: string; complexUserData: ComplexType; } const userDecoder: Decoder = object({ name: string(), complexUserData: anyJson() }); expect(userDecoder.run({name: 'Wanda', complexUserData: true})).toEqual({ ok: true, result: {name: 'Wanda', complexUserData: true} }); expect(userDecoder.run({name: 'Willard', complexUserData: 'trash data'})).toEqual({ ok: true, result: {name: 'Willard', complexUserData: 'trash data'} }); expect(userDecoder.run({name: 73, complexUserData: []})).toMatchObject({ ok: false, error: {at: 'input.name', message: 'expected a string, got a number'} }); }); }); describe('unknownJson', () => { it('accepts any values', () => { expect(unknownJson().run(1)).toEqual({ok: true, result: 1}); expect(unknownJson().run(false)).toEqual({ok: true, result: false}); expect(unknownJson().run({boots: 'n cats'})).toEqual({ok: true, result: {boots: 'n cats'}}); }); }); describe('constant', () => { it('works for string-literals', () => { const decoder: Decoder<'zero'> = constant('zero'); expect(decoder.run('zero')).toEqual({ok: true, result: 'zero'}); }); it('fails when given two different values', () => { const decoder: Decoder<42> = constant(42); expect(decoder.run(true)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected 42, got true'} }); }); it('can decode the true-literal type', () => { interface TrueValue { x: true; } const decoder: Decoder = object({x: constant(true)}); expect(decoder.run({x: true})).toEqual({ok: true, result: {x: true}}); }); it('can decode the false-literal type', () => { interface FalseValue { x: false; } const decoder: Decoder = object({x: constant(false)}); expect(decoder.run({x: false})).toEqual({ok: true, result: {x: false}}); }); it('can decode the null-literal type', () => { interface NullValue { x: null; } const decoder: Decoder = object({x: constant(null)}); expect(decoder.run({x: null})).toEqual({ok: true, result: {x: null}}); }); it('can decode a constant array', () => { const decoder: Decoder<[1, 2, 3]> = constant([1, 2, 3]); expect(decoder.run([1, 2, 3])).toEqual({ok: true, result: [1, 2, 3]}); expect(decoder.run([1, 2, 3, 4])).toMatchObject({ ok: false, error: {at: 'input', message: 'expected [1,2,3], got [1,2,3,4]'} }); }); it('can decode a constant object', () => { const decoder: Decoder<{a: true; b: 12}> = constant({a: true, b: 12}); expect(decoder.run({a: true, b: 12})).toEqual({ok: true, result: {a: true, b: 12}}); expect(decoder.run({a: true, b: 7})).toMatchObject({ ok: false, error: {at: 'input', message: 'expected {"a":true,"b":12}, got {"a":true,"b":7}'} }); }); }); describe('object', () => { describe('when given valid JSON', () => { it('can decode a simple object', () => { const decoder = object({x: number()}); expect(decoder.run({x: 5})).toMatchObject({ok: true, result: {x: 5}}); }); it('can decode a nested object', () => { const decoder = object({ payload: object({x: number(), y: number()}), error: constant(false) }); const json = {payload: {x: 5, y: 2}, error: false}; expect(decoder.run(json)).toEqual({ok: true, result: json}); }); }); describe('when given incorrect JSON', () => { it('fails when not given an object', () => { const decoder = object({x: number()}); expect(decoder.run('true')).toMatchObject({ ok: false, error: {at: 'input', message: 'expected an object, got a string'} }); }); it('fails when given an array', () => { const decoder = object({x: number()}); expect(decoder.run([])).toMatchObject({ ok: false, error: {at: 'input', message: 'expected an object, got an array'} }); }); it('reports a missing key', () => { const decoder = object({x: number()}); expect(decoder.run({})).toMatchObject({ ok: false, error: {at: 'input', message: "the key 'x' is required but was not present"} }); }); it('reports invalid values', () => { const decoder = object({name: string()}); expect(decoder.run({name: 5})).toMatchObject({ ok: false, error: {at: 'input.name', message: 'expected a string, got a number'} }); }); it('properly displays nested errors', () => { const decoder = object({ hello: object({ hey: object({ 'Howdy!': string() }) }) }); const error = decoder.run({hello: {hey: {'Howdy!': {}}}}); expect(error).toMatchObject({ ok: false, error: {at: 'input.hello.hey.Howdy!', message: 'expected a string, got an object'} }); }); }); it('ignores optional fields that decode to undefined', () => { const decoder = object({ a: number(), b: optional(string()) }); expect(decoder.run({a: 12, b: 'hats'})).toEqual({ok: true, result: {a: 12, b: 'hats'}}); expect(decoder.run({a: 12})).toEqual({ok: true, result: {a: 12}}); }); it('decodes any object when the object shape is not specified', () => { const objectKeysDecoder: Decoder = object().map(Object.keys); expect(objectKeysDecoder.run({n: 1, i: [], c: {}, e: 'e'})).toEqual({ ok: true, result: ['n', 'i', 'c', 'e'] }); }); }); describe('array', () => { const decoder = array(number()); it('works when given an array', () => { expect(decoder.run([1, 2, 3])).toEqual({ok: true, result: [1, 2, 3]}); }); it('fails when given something other than a array', () => { expect(decoder.run('oops')).toMatchObject({ ok: false, error: {at: 'input', message: 'expected an array, got a string'} }); }); describe('when given something other than an array', () => { it('fails when the elements are of the wrong type', () => { expect(decoder.run(['dang'])).toMatchObject({ ok: false, error: {at: 'input[0]', message: 'expected a number, got a string'} }); }); it('properly displays nested errors', () => { const nestedDecoder = array(array(array(number()))); expect(nestedDecoder.run([[], [], [[1, 2, 3, false]]])).toMatchObject({ ok: false, error: {at: 'input[2][0][3]', message: 'expected a number, got a boolean'} }); }); }); it('decodes any array when the array members decoder is not specified', () => { const validNumbersDecoder = array() .map((arr: unknown[]) => arr.map(number().run)) .map(Result.successes); expect(validNumbersDecoder.run([1, true, 2, 3, 'five', 4, []])).toEqual({ ok: true, result: [1, 2, 3, 4] }); expect(validNumbersDecoder.run([false, 'hi', {}])).toEqual({ok: true, result: []}); expect(validNumbersDecoder.run(false)).toMatchObject({ ok: false, error: {message: 'expected an array, got a boolean'} }); }); }); describe('tuple', () => { describe('when given valid JSON', () => { it('can decode a simple tuple', () => { const decoder: Decoder<[number, number]> = tuple([number(), number()]); expect(decoder.run([5, 6])).toMatchObject({ok: true, result: [5, 6]}); }); it('can decode tuples of mixed types', () => { const decoder: Decoder<[number, string]> = tuple([number(), string()]); expect(decoder.run([1, 'a'])).toMatchObject({ok: true, result: [1, 'a']}); }); it('can decode a nested object', () => { const decoder: Decoder<[{x: number; y: number}, false]> = tuple([ object({x: number(), y: number()}), constant(false) ]); const json = [{x: 5, y: 2}, false]; expect(decoder.run(json)).toEqual({ok: true, result: json}); }); }); describe('when given incorrect JSON', () => { it('fails when the array length does not match', () => { const decoder: Decoder<[number]> = tuple([number()]); expect(decoder.run([1, 2])).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a tuple of length 1, got one of length 2'} }); }); it('fails when given an object', () => { const decoder: Decoder<[number]> = tuple([number()]); expect(decoder.run({x: 1})).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a tuple of length 1, got an object'} }); }); it('reports invalid values', () => { const decoder: Decoder<[number, string]> = tuple([number(), string()]); expect(decoder.run([4, 5])).toMatchObject({ ok: false, error: {at: 'input[1]', message: 'expected a string, got a number'} }); }); it('properly displays nested errors', () => { const decoder: Decoder<[{hey: {'Howdy!': string}}]> = tuple([ object({ hey: object({ 'Howdy!': string() }) }) ]); const error = decoder.run([{hey: {'Howdy!': {}}}]); expect(error).toMatchObject({ ok: false, error: {at: 'input[0].hey.Howdy!', message: 'expected a string, got an object'} }); }); }); }); describe('dict', () => { describe('with a simple value decoder', () => { const decoder = dict(number()); it('can decode an empty object', () => { expect(decoder.run({})).toEqual({ok: true, result: {}}); }); it('can decode an object of with arbitrary keys', () => { expect(decoder.run({a: 1, b: 2})).toEqual({ok: true, result: {a: 1, b: 2}}); }); it('fails if a value cannot be decoded', () => { expect(decoder.run({oh: 'no'})).toMatchObject({ ok: false, error: {at: 'input.oh', message: 'expected a number, got a string'} }); }); it('fails if given an array', () => { expect(decoder.run([])).toMatchObject({ ok: false, error: {at: 'input', message: 'expected an object, got an array'} }); }); it('fails if given a primitive', () => { expect(decoder.run(5)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected an object, got a number'} }); }); }); describe('given a transformative value decoder', () => { const decoder = dict(string().map(str => str + '!')); it('transforms the values', () => { expect(decoder.run({hey: 'there', yo: 'dude'})).toEqual({ ok: true, result: {hey: 'there!', yo: 'dude!'} }); }); }); }); describe('optional', () => { describe('decoding a non-object type', () => { const decoder = optional(number()); it('can decode the given type', () => { expect(decoder.run(5)).toEqual({ok: true, result: 5}); }); it('can decode undefined', () => { expect(decoder.run(undefined)).toEqual({ok: true, result: undefined}); }); it('fails when the value is invalid', () => { expect(decoder.run(false)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a number, got a boolean'} }); }); }); describe('decoding an interface with optional fields', () => { interface User { id: number; isDog?: boolean; } const decoder: Decoder = object({ id: number(), isDog: optional(boolean()) }); it('can decode the object when the optional field is present', () => { expect(decoder.run({id: 1, isDog: true})).toEqual({ok: true, result: {id: 1, isDog: true}}); }); it('can decode the object when the optional field is missing', () => { expect(decoder.run({id: 2})).toEqual({ok: true, result: {id: 2}}); }); it('fails when the optional field is invalid', () => { const error = decoder.run({id: 3, isDog: 'supdog'}); expect(error).toMatchObject({ ok: false, error: {at: 'input.isDog', message: 'expected a boolean, got a string'} }); }); }); }); describe('oneOf', () => { describe('when given valid input', () => { it('can decode a value with a single alternative', () => { const decoder = oneOf(string()); expect(decoder.run('yo')).toEqual({ok: true, result: 'yo'}); }); it('can decode a value with multiple alternatives', () => { const decoder = array(oneOf(string().map(s => s.length), number())); expect(decoder.run(['hey', 10])).toEqual({ok: true, result: [3, 10]}); }); }); it('fails when a value does not match any decoder', () => { const decoder = oneOf(string(), number().map(String)); expect(decoder.run([])).toMatchObject({ ok: false, error: { at: 'input', message: 'expected a value matching one of the decoders, got the errors ' + '["at error: expected a string, got an array", "at error: expected a number, got an array"]' } }); }); it('fails and reports errors for nested values', () => { const decoder = array( oneOf(valueAt([1, 'a', 'b'], number()), valueAt([1, 'a', 'x'], number())) ); expect(decoder.run([[{}, {a: {b: true}}]])).toMatchObject({ ok: false, error: { at: 'input[0]', message: 'expected a value matching one of the decoders, got the errors ' + '["at error[1].a.b: expected a number, got a boolean", ' + '"at error[1].a.x: path does not exist"]' } }); }); it('can act as the union function when given the correct annotation', () => { type C = {a: string} | {b: number}; const decoder: Decoder = oneOf(object({a: string()}), object({b: number()})); expect(decoder.run({a: 'xyz'})).toEqual({ok: true, result: {a: 'xyz'}}); }); }); describe('union', () => { interface A { kind: 'a'; value: number; } interface B { kind: 'b'; value: boolean; } type C = A | B; const decoder: Decoder = union( object({kind: constant('a'), value: number()}), object({kind: constant('b'), value: boolean()}) ); it('can decode a value that matches one of the union types', () => { const json = {kind: 'a', value: 12}; expect(decoder.run(json)).toEqual({ok: true, result: json}); }); it('fails when a value does not match any decoders', () => { const error = decoder.run({kind: 'b', value: 12}); expect(error).toMatchObject({ ok: false, error: { at: 'input', message: 'expected a value matching one of the decoders, got the errors ' + '["at error.kind: expected "a", got "b"", "at error.value: expected a boolean, got a number"]' } }); }); }); describe('intersection', () => { it('uses two decoders to decode an extended interface', () => { interface A { a: number; } interface AB extends A { b: string; } const aDecoder: Decoder = object({a: number()}); const abDecoder: Decoder = intersection(aDecoder, object({b: string()})); expect(abDecoder.run({a: 12, b: '!!!'})).toEqual({ok: true, result: {a: 12, b: '!!!'}}); }); it('can combine many decoders', () => { interface UVWXYZ { u: true; v: string[]; w: boolean | null; x: number; y: string; z: boolean; } const uvwxyzDecoder: Decoder = intersection( object({u: constant(true)}), object({v: array(string())}), object({w: union(boolean(), constant(null))}), object({x: number()}), object({y: string(), z: boolean()}) ); expect(uvwxyzDecoder.run({u: true, v: [], w: null, x: 4, y: 'y', z: false})).toEqual({ ok: true, result: {u: true, v: [], w: null, x: 4, y: 'y', z: false} }); }); }); describe('withDefault', () => { const decoder = withDefault('puppies', string()); it('uses the json value when decoding is successful', () => { expect(decoder.run('pancakes')).toEqual({ok: true, result: 'pancakes'}); }); it('uses the default when the decoder fails', () => { expect(decoder.run(5)).toEqual({ok: true, result: 'puppies'}); }); }); describe('valueAt', () => { describe('decode an value', () => { it('can decode a single object field', () => { const decoder = valueAt(['a'], string()); expect(decoder.run({a: 'boots', b: 'cats'})).toEqual({ok: true, result: 'boots'}); }); it('can decode a single array value', () => { const decoder = valueAt([1], string()); expect(decoder.run(['boots', 'cats'])).toEqual({ok: true, result: 'cats'}); }); }); describe('decode a nested path', () => { const decoder = valueAt(['a', 1, 'b'], string()); it('can decode a field in a nested structure', () => { expect(decoder.run({a: [{}, {b: 'surprise!'}]})).toEqual({ok: true, result: 'surprise!'}); }); it('fails when an array path does not exist', () => { expect(decoder.run({a: []})).toMatchObject({ ok: false, error: {at: 'input.a[1].b', message: 'path does not exist'} }); }); it('fails when an object path does not exist', () => { expect(decoder.run({x: 12})).toMatchObject({ ok: false, error: {at: 'input.a[1]', message: 'path does not exist'} }); }); it('fails when the decoder fails at the end of the path', () => { expect(decoder.run({a: ['a', {b: 12}]})).toMatchObject({ ok: false, error: {at: 'input.a[1].b', message: 'expected a string, got a number'} }); }); }); describe('decode an optional field', () => { const decoder = valueAt(['a', 'b', 'c'], optional(string())); it('fails when the path does not exist', () => { const error = decoder.run({a: {x: 'cats'}}); expect(error).toMatchObject({ ok: false, error: {at: 'input.a.b.c', message: 'path does not exist'} }); }); it('succeeds when the final field is not found', () => { expect(decoder.run({a: {b: {z: 1}}})).toEqual({ok: true, result: undefined}); }); }); describe('non-object json', () => { it('only accepts json objects and arrays', () => { const decoder = valueAt(['a'], string()); expect(decoder.run('abc')).toMatchObject({ ok: false, error: {at: 'input.a', message: 'expected an object, got a string'} }); expect(decoder.run(true)).toMatchObject({ ok: false, error: {at: 'input.a', message: 'expected an object, got a boolean'} }); }); it('fails when a feild in the path does not correspond to a json object', () => { const decoder = valueAt(['a', 'b', 'c'], string()); const error = decoder.run({a: {b: 1}}); expect(error).toMatchObject({ ok: false, error: {at: 'input.a.b.c', message: 'expected an object, got a number'} }); }); it('fails when an index in the path does not correspond to a json array', () => { const decoder = valueAt([0, 0, 1], string()); const error = decoder.run([[false]]); expect(error).toMatchObject({ ok: false, error: {at: 'input[0][0][1]', message: 'expected an array, got a boolean'} }); }); }); it('decodes the input when given an empty path', () => { const decoder = valueAt([], number()); expect(decoder.run(12)).toEqual({ok: true, result: 12}); }); }); describe('succeed', () => { const decoder = succeed(12345); it('always decodes the input as the same value', () => { expect(decoder.run('pancakes')).toEqual({ok: true, result: 12345}); expect(decoder.run(5)).toEqual({ok: true, result: 12345}); }); }); describe('fail', () => { const wisdom = 'People don’t think it be like it is, but it do.'; const decoder = fail(wisdom); it('always fails and returns the same error message', () => { expect(decoder.run('pancakes')).toMatchObject({ ok: false, error: {at: 'input', message: wisdom} }); expect(decoder.run(5)).toMatchObject({ok: false, error: {at: 'input', message: wisdom}}); }); }); describe('lazy', () => { describe('decoding a primitive data type', () => { const decoder = lazy(() => string()); it('can decode type as normal', () => { expect(decoder.run('hello')).toEqual({ok: true, result: 'hello'}); }); it('does not alter the error message', () => { expect(decoder.run(5)).toMatchObject({ ok: false, error: {at: 'input', message: 'expected a string, got a number'} }); }); }); describe('decoding a recursive data structure', () => { interface Comment { msg: string; replies: Comment[]; } const decoder: Decoder = object({ msg: string(), replies: lazy(() => array(decoder)) }); it('can decode the data structure', () => { const tree = {msg: 'hey', replies: [{msg: 'hi', replies: []}]}; expect(decoder.run(tree)).toEqual({ ok: true, result: {msg: 'hey', replies: [{msg: 'hi', replies: []}]} }); }); it('fails when a nested value is invalid', () => { const badTree = {msg: 'hey', replies: [{msg: 'hi', replies: ['hello']}]}; expect(decoder.run(badTree)).toMatchObject({ ok: false, error: {at: 'input.replies[0].replies[0]', message: 'expected an object, got a string'} }); }); }); }); describe('runPromise', () => { const promise = (json: unknown): Promise => boolean().runPromise(json); it('resolves the promise when the decoder succeeds', () => { return expect(promise(true)).resolves.toBe(true); }); it('rejects the promise when the decoder fails', () => { return expect(promise(42)).rejects.toEqual({ kind: 'DecoderError', input: 42, at: 'input', message: 'expected a boolean, got a number' }); }); it('returns a DecoderError when the decoder fails', () => { return expect(promise(42).catch(e => isDecoderError(e))).resolves.toBeTruthy(); }); }); describe('runWithException', () => { const decoder = boolean(); it('can run a decoder and return the successful value', () => { expect(decoder.runWithException(false)).toBe(false); }); it('throws an exception when the decoder fails', () => { let thrownError: any; try { decoder.runWithException(42); } catch (e) { thrownError = e; } expect(thrownError).toEqual({ kind: 'DecoderError', input: 42, at: 'input', message: 'expected a boolean, got a number' }); }); }); describe('map', () => { it('can apply the identity function to the decoder', () => { const decoder = string().map(x => x); expect(decoder.run('hey there')).toEqual({ok: true, result: 'hey there'}); }); it('can apply an endomorphic function to the decoder', () => { const decoder = number().map(x => x * 5); expect(decoder.run(10)).toEqual({ok: true, result: 50}); }); it('can apply a function that transforms the type', () => { const decoder = string().map(x => x.length); expect(decoder.run('hey')).toEqual({ok: true, result: 3}); }); }); describe('andThen', () => { describe('creates decoders based on previous results', () => { 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.`); } }); it('can decode using both the first and second decoder', () => { expect(decoder.run({version: 5, x: 'bootsncats'})).toMatchObject({ ok: false, error: {at: 'input', message: 'Unable to decode info, version 5 is not supported.'} }); expect(decoder.run({version: 3, a: true})).toEqual({ok: true, result: {a: true}}); }); it('fails when the first decoder fails', () => { expect(decoder.run({version: null, a: true})).toMatchObject({ ok: false, error: {at: 'input.version', message: 'expected a number, got null'} }); }); it('fails when the second decoder fails', () => { const json = {version: 3, a: 1}; expect(decoder.run(json)).toMatchObject({ ok: false, error: {at: 'input.a', message: 'expected a boolean, got a number'} }); }); }); it('creates decoders for custom types', () => { type NonEmptyArray = T[] & {__nonEmptyArrayBrand__: void}; const createNonEmptyArray = (arr: T[]): NonEmptyArray => arr as NonEmptyArray; const nonEmptyArrayDecoder = (values: Decoder): Decoder> => array(values).andThen( arr => arr.length > 0 ? succeed(createNonEmptyArray(arr)) : fail(`expected a non-empty array, got an empty array`) ); expect(nonEmptyArrayDecoder(number()).run([1, 2, 3])).toEqual({ ok: true, result: [1, 2, 3] }); expect(nonEmptyArrayDecoder(number()).run([])).toMatchObject({ ok: false, error: {message: 'expected a non-empty array, got an empty array'} }); }); }); describe('where', () => { const chars = (length: number): Decoder => string().where((s: string) => s.length === length, `expected a string of length ${length}`); const range = (min: number, max: number): Decoder => number().where( (n: number) => n >= min && n <= max, `expected a number between ${min} and ${max}` ); it('can test for strings of a given length', () => { expect(chars(7).run('7777777')).toEqual({ok: true, result: '7777777'}); expect(chars(7).run('666666')).toMatchObject({ ok: false, error: {message: 'expected a string of length 7'} }); }); it('can test for numbers in a given range', () => { expect(range(1, 9).run(7)).toEqual({ok: true, result: 7}); expect(range(1, 9).run(12)).toMatchObject({ ok: false, error: {message: 'expected a number between 1 and 9'} }); }); it('reports when the base decoder fails', () => { expect(chars(7).run(false)).toMatchObject({ ok: false, error: {message: 'expected a string, got a boolean'} }); expect(range(0, 1).run(null)).toMatchObject({ ok: false, error: {message: 'expected a number, got null'} }); }); }); describe('Result', () => { describe('can run a decoder with default value', () => { const decoder = number(); it('succeeds with the value', () => { expect(Result.withDefault(0, decoder.run(12))).toEqual(12); }); it('succeeds with the default value instead of failing', () => { expect(Result.withDefault(0, decoder.run('999'))).toEqual(0); }); }); it('can return successes from an array of decoded values', () => { const json: unknown = [1, true, 2, 3, 'five', 4, []]; const jsonArray: unknown[] = Result.withDefault([], array().run(json)); const numbers: number[] = Result.successes(jsonArray.map(number().run)); expect(numbers).toEqual([1, 2, 3, 4]); }); }); ================================================ FILE: test/phone-example.test.ts ================================================ import { Decoder, string, number, constant, object, array, optional, oneOf, union } from '../src/index'; describe('decode phone number objects', () => { enum PhoneUse { Mobile = 'Mobile', Home = 'Home', Work = 'Work' } interface PhoneNumber { id: number; use?: PhoneUse; } interface InternationalPhone extends PhoneNumber { international: true; rawNumber: string; } interface DomesticPhone extends PhoneNumber { international: false; areaCode: string; prefix: string; lineNumber: string; } type Phone = DomesticPhone | InternationalPhone; const phoneUseDecoder: Decoder = oneOf( constant(PhoneUse.Mobile), constant(PhoneUse.Home), constant(PhoneUse.Work) ); const internationalPhoneDecoder: Decoder = object({ id: number(), use: optional(phoneUseDecoder), international: constant(true), rawNumber: string() }); const domesticPhoneDecoder: Decoder = object({ id: number(), use: optional(phoneUseDecoder), international: constant(false), areaCode: string(), prefix: string(), lineNumber: string() }); const phoneDecoder: Decoder = union(domesticPhoneDecoder, internationalPhoneDecoder); const phonesDecoder: Decoder = array(phoneDecoder); it('can decode both international and domestic phones', () => { const json = [ { id: 1, use: 'Work', international: false, areaCode: '123', prefix: '456', lineNumber: '7890' }, { id: 2, use: 'Work', international: true, rawNumber: '111234567890' }, { id: 3, international: false, areaCode: '000', prefix: '000', lineNumber: '5555' } ]; expect(phonesDecoder.run(json)).toEqual({ok: true, result: json}); }); it('fails when an object is neither an international or domestic phone', () => { const json = [ { id: 1, use: 'Work', international: false, areaCode: '123', prefix: '456', lineNumber: '7890' }, { id: 5 } ]; const error = phonesDecoder.run(json); expect(error).toMatchObject({ ok: false, error: { at: 'input[1]', message: [ 'expected a value matching one of the decoders, got the errors ', `["at error: the key 'international' is required but was not present", `, `"at error: the key 'international' is required but was not present"]` ].join('') } }); }); }); ================================================ FILE: test/tagged-json-example.test.ts ================================================ import { Decoder, string, number, boolean, constant, array, dict, union, lazy } from '../src/index'; describe('create tagged json objects', () => { type TaggedJson = | {tag: 'null'; value: null} | {tag: 'string'; value: string} | {tag: 'number'; value: number} | {tag: 'boolean'; value: boolean} | {tag: 'array'; value: Array} | {tag: 'object'; value: {[name: string]: TaggedJson}}; const json: any = [{x: 1, y: 5}, {a: true, b: 'false'}, 1, true]; const taggedJsonDecoder: Decoder = union( constant(null).map(value => ({tag: 'null', value: value})), string().map(value => ({tag: 'string', value: value})), number().map(value => ({tag: 'number', value: value})), boolean().map(value => ({tag: 'boolean', value: value})), lazy(() => array(taggedJsonDecoder).map(value => ({tag: 'array', value: value}))), lazy(() => dict(taggedJsonDecoder).map(value => ({tag: 'object', value: value}))) ); it('maps json to tagged json', () => { expect(taggedJsonDecoder.run(json)).toEqual({ ok: true, result: { tag: 'array', value: [ { tag: 'object', value: { x: {tag: 'number', value: 1}, y: {tag: 'number', value: 5} } }, { tag: 'object', value: { a: { tag: 'boolean', value: true }, b: { tag: 'string', value: 'false' } } }, { tag: 'number', value: 1 }, { tag: 'boolean', value: true } ] } }); }); }); ================================================ FILE: test/user-example.test.ts ================================================ import {Decoder, string, number, boolean, object} from '../src/index'; describe('decode json as User interface', () => { interface User { firstname: string; lastname: string; age: number; active: boolean; } const userJson: any = { firstname: 'John', lastname: 'Doe', age: 99, active: false }; const invalidUserJson: any = { firstname: 'John', lastName: 'Doe', // invalid camelCase age: 99, active: false }; const userDecoder: Decoder = object({ firstname: string(), lastname: string(), age: number(), active: boolean() }); it('successfuly passes through the valid user object', () => { expect(userDecoder.run(userJson)).toEqual({ ok: true, result: userJson }); }); it('fails when a required key is missing', () => { const error = userDecoder.run(invalidUserJson); expect(error).toMatchObject({ ok: false, error: {at: 'input', message: "the key 'lastname' is required but was not present"} }); }); }); ================================================ FILE: tsconfig-test.json ================================================ { "compilerOptions": { "moduleResolution": "node", "target": "es5", "module": "es2015", "lib": ["es2015", "es2016", "es2017", "dom"], "strict": true, "sourceMap": true, "declaration": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "declarationDir": "dist/types", "outDir": "dist/es", "typeRoots": ["node_modules/@types"] }, "include": ["src", "test"] } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "moduleResolution": "node", "target": "es5", "module": "es2015", "lib": ["es2015", "es2016", "es2017", "dom"], "strict": true, "sourceMap": true, "declaration": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "declarationDir": "dist/types", "outDir": "dist/es", "typeRoots": ["node_modules/@types"] }, "include": ["src"] } ================================================ FILE: tslint.json ================================================ { "extends": [ "tslint-config-standard", "tslint-config-prettier" ], "rules": { "strict-type-predicates": false } }