├── .prettierignore ├── nix-files ├── shell.nix ├── nixpkgs.nix └── default.nix ├── .gitignore ├── docs ├── modules │ ├── _index_.md │ ├── _decoder_.md │ ├── _combinators_.md │ └── _result_.md ├── interfaces │ ├── _result_.ok.md │ ├── _result_.err.md │ └── _decoder_.decodererror.md ├── README.md └── classes │ └── _decoder_.decoder.md ├── tslint.json ├── .prettierrc ├── .npmignore ├── .travis.yml ├── src ├── index.ts ├── combinators.ts ├── result.ts └── decoder.ts ├── tsconfig.json ├── tsconfig-test.json ├── rollup.config.ts ├── LICENSE ├── test ├── user-example.test.ts ├── tagged-json-example.test.ts ├── phone-example.test.ts └── json-decode.test.ts ├── DOCS.md ├── bin └── jtv ├── package.json └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | dist/ 3 | -------------------------------------------------------------------------------- /nix-files/shell.nix: -------------------------------------------------------------------------------- 1 | { nixpkgsFn ? import ./nixpkgs.nix 2 | , package ? ./default.nix 3 | }: 4 | (import package { inherit nixpkgsFn; }) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | .DS_Store 5 | *.log 6 | .vscode 7 | .idea 8 | dist 9 | compiled 10 | .awcache 11 | .rpt2_cache 12 | gc-roots 13 | -------------------------------------------------------------------------------- /docs/modules/_index_.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["index"](../modules/_index_.md) 2 | 3 | # External module: "index" 4 | 5 | ## Index 6 | 7 | --- 8 | 9 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-standard", 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "strict-type-predicates": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | tabWidth: 2 3 | useTabs: false 4 | semi: true 5 | singleQuote: true 6 | trailingComma: none 7 | bracketSpacing: false 8 | arrowParens: avoid 9 | parser: typescript 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .rpt2_cache/ 3 | bin/ 4 | coverage/ 5 | nix-files/ 6 | node_modules/ 7 | src/ 8 | test/ 9 | .gitignore 10 | .prettierrc 11 | .travis.yml 12 | rollup.config.ts 13 | tags 14 | tsconfig-test.json 15 | tsconfig.json 16 | tslint.json 17 | yarn.lock 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - node 10 | script: 11 | - npm run test:prod && npm run build 12 | after_success: 13 | - npm run report-coverage 14 | -------------------------------------------------------------------------------- /nix-files/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | let source = '' 2 | { 3 | "owner": "NixOS", 4 | "repo": "nixpkgs-channels", 5 | "rev": "aebdc892d6aa6834a083fb8b56c43578712b0dab", 6 | "sha256": "1bcpjc7f1ff5k7vf5rwwb7g7m4j238hi4ssnx7xqglr7hj4ms0cz" 7 | } 8 | ''; 9 | in 10 | import ((import {}).fetchFromGitHub (builtins.fromJSON (source))) 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Result from './result'; 2 | export {Result}; 3 | 4 | export {Decoder, DecoderError, isDecoderError, DecoderObject} from './decoder'; 5 | 6 | export { 7 | string, 8 | number, 9 | boolean, 10 | anyJson, 11 | unknownJson, 12 | constant, 13 | object, 14 | array, 15 | tuple, 16 | dict, 17 | optional, 18 | oneOf, 19 | union, 20 | intersection, 21 | withDefault, 22 | valueAt, 23 | succeed, 24 | fail, 25 | lazy 26 | } from './combinators'; 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "module": "es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "declarationDir": "dist/types", 14 | "outDir": "dist/es", 15 | "typeRoots": ["node_modules/@types"] 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "module": "es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "declarationDir": "dist/types", 14 | "outDir": "dist/es", 15 | "typeRoots": ["node_modules/@types"] 16 | }, 17 | "include": ["src", "test"] 18 | } 19 | -------------------------------------------------------------------------------- /docs/interfaces/_result_.ok.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["result"](../modules/_result_.md) > [Ok](../interfaces/_result_.ok.md) 2 | 3 | # Interface: Ok 4 | 5 | The success type variant for `Result`. Denotes that a result value was computed with no errors. 6 | 7 | ## Type parameters 8 | #### V 9 | ## Hierarchy 10 | 11 | **Ok** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [ok](_result_.ok.md#ok) 18 | * [result](_result_.ok.md#result) 19 | 20 | --- 21 | 22 | ## Properties 23 | 24 | 25 | 26 | ### ok 27 | 28 | **● ok**: *`true`* 29 | 30 | ___ 31 | 32 | 33 | ### result 34 | 35 | **● result**: *`V`* 36 | 37 | ___ 38 | 39 | -------------------------------------------------------------------------------- /docs/interfaces/_result_.err.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["result"](../modules/_result_.md) > [Err](../interfaces/_result_.err.md) 2 | 3 | # Interface: Err 4 | 5 | The error type variant for `Result`. Denotes that some error occurred before the result was computed. 6 | 7 | ## Type parameters 8 | #### E 9 | ## Hierarchy 10 | 11 | **Err** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [error](_result_.err.md#error) 18 | * [ok](_result_.err.md#ok) 19 | 20 | --- 21 | 22 | ## Properties 23 | 24 | 25 | 26 | ### error 27 | 28 | **● error**: *`E`* 29 | 30 | ___ 31 | 32 | 33 | ### ok 34 | 35 | **● ok**: *`false`* 36 | 37 | ___ 38 | 39 | -------------------------------------------------------------------------------- /nix-files/default.nix: -------------------------------------------------------------------------------- 1 | { nixpkgsFn ? import ./nixpkgs.nix 2 | , system ? null }: 3 | let nixpkgs = nixpkgsFn ({ 4 | # extra config goes here 5 | } // ( if system == null then {} else { inherit system; } )); 6 | in 7 | nixpkgs.stdenv.mkDerivation { 8 | name = "json-type-validator"; 9 | buildInputs = with nixpkgs; [ nodejs yarn git ]; 10 | src = "./"; 11 | 12 | builder = builtins.toFile "builder.sh" '' 13 | echo "Use this derivation with nix-shell only" 14 | 15 | exit 1 16 | ''; 17 | 18 | shellHook = '' 19 | # Get to the source directory 20 | cd $src 21 | # Install any new dependencies 22 | yarn 23 | # Add node_modules to path 24 | export PATH=$src/node_modules/bin:$PATH 25 | ''; 26 | } 27 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import sourceMaps from 'rollup-plugin-sourcemaps'; 3 | import typescript from 'rollup-plugin-typescript2'; 4 | 5 | const pkg = require('./package.json'); 6 | 7 | export default { 8 | input: `src/index.ts`, 9 | output: [ 10 | { 11 | file: pkg.main, 12 | name: 'index', 13 | format: 'cjs', 14 | sourcemap: true 15 | }, 16 | { 17 | file: pkg.module, 18 | format: 'es', 19 | sourcemap: true 20 | } 21 | ], 22 | external: id => { 23 | return id.includes('/node_modules/'); 24 | }, 25 | watch: { 26 | include: 'src/**' 27 | }, 28 | plugins: [ 29 | // Compile TypeScript files 30 | typescript({ 31 | useTsconfigDeclarationDir: true 32 | }), 33 | resolve(), 34 | 35 | // Resolve source maps to the original source 36 | sourceMaps() 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Elias Mulhall 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/interfaces/_decoder_.decodererror.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) > [DecoderError](../interfaces/_decoder_.decodererror.md) 2 | 3 | # Interface: DecoderError 4 | 5 | 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. 6 | 7 | ## Hierarchy 8 | 9 | **DecoderError** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [at](_decoder_.decodererror.md#at) 16 | * [input](_decoder_.decodererror.md#input) 17 | * [kind](_decoder_.decodererror.md#kind) 18 | * [message](_decoder_.decodererror.md#message) 19 | 20 | --- 21 | 22 | ## Properties 23 | 24 | 25 | 26 | ### at 27 | 28 | **● at**: *`string`* 29 | 30 | ___ 31 | 32 | 33 | ### input 34 | 35 | **● input**: *`unknown`* 36 | 37 | ___ 38 | 39 | 40 | ### kind 41 | 42 | **● kind**: *"DecoderError"* 43 | 44 | ___ 45 | 46 | 47 | ### message 48 | 49 | **● message**: *`string`* 50 | 51 | ___ 52 | 53 | -------------------------------------------------------------------------------- /test/user-example.test.ts: -------------------------------------------------------------------------------- 1 | import {Decoder, string, number, boolean, object} from '../src/index'; 2 | 3 | describe('decode json as User interface', () => { 4 | interface User { 5 | firstname: string; 6 | lastname: string; 7 | age: number; 8 | active: boolean; 9 | } 10 | 11 | const userJson: any = { 12 | firstname: 'John', 13 | lastname: 'Doe', 14 | age: 99, 15 | active: false 16 | }; 17 | 18 | const invalidUserJson: any = { 19 | firstname: 'John', 20 | lastName: 'Doe', // invalid camelCase 21 | age: 99, 22 | active: false 23 | }; 24 | 25 | const userDecoder: Decoder = object({ 26 | firstname: string(), 27 | lastname: string(), 28 | age: number(), 29 | active: boolean() 30 | }); 31 | 32 | it('successfuly passes through the valid user object', () => { 33 | expect(userDecoder.run(userJson)).toEqual({ 34 | ok: true, 35 | result: userJson 36 | }); 37 | }); 38 | 39 | it('fails when a required key is missing', () => { 40 | const error = userDecoder.run(invalidUserJson); 41 | expect(error).toMatchObject({ 42 | ok: false, 43 | error: {at: 'input', message: "the key 'lastname' is required but was not present"} 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/combinators.ts: -------------------------------------------------------------------------------- 1 | import {Decoder} from './decoder'; 2 | 3 | /* tslint:disable:variable-name */ 4 | 5 | /** See `Decoder.string` */ 6 | export const string = Decoder.string; 7 | 8 | /** See `Decoder.number` */ 9 | export const number = Decoder.number; 10 | 11 | /** See `Decoder.boolean` */ 12 | export const boolean = Decoder.boolean; 13 | 14 | /** See `Decoder.anyJson` */ 15 | export const anyJson = Decoder.anyJson; 16 | 17 | /** See `Decoder.unknownJson` */ 18 | export const unknownJson: () => Decoder = Decoder.unknownJson; 19 | 20 | /** See `Decoder.constant` */ 21 | export const constant = Decoder.constant; 22 | 23 | /** See `Decoder.object` */ 24 | export const object = Decoder.object; 25 | 26 | /** See `Decoder.array` */ 27 | export const array = Decoder.array; 28 | 29 | /** See `Decoder.tuple` */ 30 | export const tuple = Decoder.tuple; 31 | 32 | /** See `Decoder.dict` */ 33 | export const dict = Decoder.dict; 34 | 35 | /** See `Decoder.optional` */ 36 | export const optional = Decoder.optional; 37 | 38 | /** See `Decoder.oneOf` */ 39 | export const oneOf = Decoder.oneOf; 40 | 41 | /** See `Decoder.union` */ 42 | export const union = Decoder.union; 43 | 44 | /** See `Decoder.intersection` */ 45 | export const intersection = Decoder.intersection; 46 | 47 | /** See `Decoder.withDefault` */ 48 | export const withDefault = Decoder.withDefault; 49 | 50 | /** See `Decoder.valueAt` */ 51 | export const valueAt = Decoder.valueAt; 52 | 53 | /** See `Decoder.succeed` */ 54 | export const succeed = Decoder.succeed; 55 | 56 | /** See `Decoder.fail` */ 57 | export const fail = Decoder.fail; 58 | 59 | /** See `Decoder.lazy` */ 60 | export const lazy = Decoder.lazy; 61 | -------------------------------------------------------------------------------- /docs/modules/_decoder_.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) 2 | 3 | # External module: "decoder" 4 | 5 | ## Index 6 | 7 | ### Classes 8 | 9 | * [Decoder](../classes/_decoder_.decoder.md) 10 | 11 | ### Interfaces 12 | 13 | * [DecoderError](../interfaces/_decoder_.decodererror.md) 14 | 15 | ### Type aliases 16 | 17 | * [DecoderObject](_decoder_.md#decoderobject) 18 | 19 | ### Functions 20 | 21 | * [isDecoderError](_decoder_.md#isdecodererror) 22 | 23 | --- 24 | 25 | ## Type aliases 26 | 27 | 28 | 29 | ### DecoderObject 30 | 31 | **ΤDecoderObject**: *`object`* 32 | 33 | 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. 34 | 35 | Example: 36 | 37 | ``` 38 | interface X { 39 | a: boolean; 40 | b: string; 41 | } 42 | 43 | const decoderObject: DecoderObject = { 44 | a: boolean(), 45 | b: string() 46 | } 47 | ``` 48 | 49 | #### Type declaration 50 | 51 | ___ 52 | 53 | ## Functions 54 | 55 | 56 | 57 | ### `` isDecoderError 58 | 59 | ▸ **isDecoderError**(a: *`any`*): `boolean` 60 | 61 | 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. 62 | 63 | **Parameters:** 64 | 65 | | Param | Type | 66 | | ------ | ------ | 67 | | a | `any` | 68 | 69 | **Returns:** `boolean` 70 | 71 | ___ 72 | 73 | -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | [Documentation](https://github.com/mojotech/json-type-validation/tree/master/docs). 4 | 5 | The best places to start are with the examples in the `test/` directory, and the 6 | documentation for the 7 | [Decoder class](https://github.com/mojotech/json-type-validation/blob/master/docs/classes/_decoder_.decoder.md). 8 | At some point you may need documentation for dealing with the 9 | [Result type](https://github.com/mojotech/json-type-validation/blob/master/docs/modules/_result_.md). 10 | 11 | ### Type Parameters 12 | 13 | Many of the decoder functions take an optional type parameter which determines 14 | the type of the decoded value. In most cases typescript successfully infers 15 | these types, although some specific decoders include documentation for 16 | situations where the type is necessary (see the `constant` and `union` 17 | decoders). You may still find that including the type parameter improves type 18 | inference in situations where typescript's error messages are particularly 19 | unhelpful. 20 | 21 | As an example, a decoder for the `Pet` interface can be typechecked just as 22 | effectively using the type parameter as with the `Decoder` annotation. 23 | ``` 24 | const petDecoder = object({ 25 | name: string(), 26 | species: string(), 27 | age: optional(number()), 28 | isCute: optional(boolean()) 29 | }) 30 | ``` 31 | 32 | ### Combinators 33 | 34 | This library uses the [combinator pattern](https://wiki.haskell.org/Combinator_pattern) 35 | to build decoders. The decoder primitives `string`, `number`, `boolean`, 36 | `anyJson`, `constant`, `succeed`, and `fail` act as decoder building blocks that 37 | each perform a simple decoding operation. The decoder combinators `object`, 38 | `array`, `dict`, `optional`, `oneOf`, `union`, `withDefault`, `valueAt`, and 39 | `lazy` take decoders as arguments, and combined the decoders into more 40 | complicated structures. You can think of your own user-defined decoders as an 41 | extension of these composable units. 42 | -------------------------------------------------------------------------------- /bin/jtv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPTNAME=$(basename $0) 4 | DIRNAME=$(dirname $0) 5 | MODE=$1 6 | 7 | shift 8 | 9 | print_help() { 10 | echo "Usage: $SCRIPTNAME MODE" 11 | echo "Enter a shell with all dependencies required for MODE." 12 | echo "MODE is one of:" 13 | echo " build-shell - enter a shell for building the library" 14 | echo " build-watch - enter a build shell and run \`yarn start\`" 15 | echo " distribute - build the library and exist" 16 | echo " run COMMAND - enter a build shell and run the specified command" 17 | echo " help, --help, -h - display this help message" 18 | } 19 | if [ "$MODE" = "help" -o "$MODE" = "--help" -o "$MODE" = "-h" ]; then 20 | print_help 21 | exit 0 22 | fi 23 | 24 | if [ "$MODE" = "build-shell" ]; then 25 | echo "Starting build shell..." 26 | # --add-root, --indirect place a garbage collection root outside of the main 27 | # nix store directory. This allows you to run nix-collect-garbage without 28 | # collecting the dependencies for this package. To collect the dependencies 29 | # for this package, delete the roots in gc-roots. 30 | # --pure limits the generated shell to only those dependencies specified in 31 | # the nix file. 32 | nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix 33 | elif [ "$MODE" = "build-watch" ]; then 34 | echo "Starting build watcher..." 35 | nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix --run "yarn start" 36 | elif [ "$MODE" = "distribute" ]; then 37 | echo "Building library for distribution..." 38 | nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix --run "yarn build" 39 | 40 | elif [ "$MODE" = "run" ]; then 41 | echo "Running \`$@\` in build environment..." 42 | nix-shell --pure --add-root $DIRNAME/../gc-roots/json-type-validation.drv --indirect $DIRNAME/../nix-files/shell.nix --run "$*" 43 | else 44 | echo "Must specify MODE!" 45 | print_help 46 | exit 1 47 | fi 48 | -------------------------------------------------------------------------------- /test/tagged-json-example.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Decoder, 3 | string, 4 | number, 5 | boolean, 6 | constant, 7 | array, 8 | dict, 9 | union, 10 | lazy 11 | } from '../src/index'; 12 | 13 | describe('create tagged json objects', () => { 14 | type TaggedJson = 15 | | {tag: 'null'; value: null} 16 | | {tag: 'string'; value: string} 17 | | {tag: 'number'; value: number} 18 | | {tag: 'boolean'; value: boolean} 19 | | {tag: 'array'; value: Array} 20 | | {tag: 'object'; value: {[name: string]: TaggedJson}}; 21 | 22 | const json: any = [{x: 1, y: 5}, {a: true, b: 'false'}, 1, true]; 23 | 24 | const taggedJsonDecoder: Decoder = union( 25 | constant(null).map(value => ({tag: 'null', value: value})), 26 | string().map(value => ({tag: 'string', value: value})), 27 | number().map(value => ({tag: 'number', value: value})), 28 | boolean().map(value => ({tag: 'boolean', value: value})), 29 | lazy(() => array(taggedJsonDecoder).map(value => ({tag: 'array', value: value}))), 30 | lazy(() => dict(taggedJsonDecoder).map(value => ({tag: 'object', value: value}))) 31 | ); 32 | 33 | it('maps json to tagged json', () => { 34 | expect(taggedJsonDecoder.run(json)).toEqual({ 35 | ok: true, 36 | result: { 37 | tag: 'array', 38 | value: [ 39 | { 40 | tag: 'object', 41 | value: { 42 | x: {tag: 'number', value: 1}, 43 | y: {tag: 'number', value: 5} 44 | } 45 | }, 46 | { 47 | tag: 'object', 48 | value: { 49 | a: { 50 | tag: 'boolean', 51 | value: true 52 | }, 53 | b: { 54 | tag: 'string', 55 | value: 'false' 56 | } 57 | } 58 | }, 59 | { 60 | tag: 'number', 61 | value: 1 62 | }, 63 | { 64 | tag: 'boolean', 65 | value: true 66 | } 67 | ] 68 | } 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | Documentation 3 | ============= 4 | 5 | [Documentation](https://github.com/mojotech/json-type-validation/tree/master/docs). 6 | 7 | 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). 8 | 9 | ### Type Parameters 10 | 11 | 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. 12 | 13 | As an example, a decoder for the `Pet` interface can be typechecked just as effectively using the type parameter as with the `Decoder` annotation. 14 | 15 | ``` 16 | const petDecoder = object({ 17 | name: string(), 18 | species: string(), 19 | age: optional(number()), 20 | isCute: optional(boolean()) 21 | }) 22 | ``` 23 | 24 | ### Combinators 25 | 26 | 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. 27 | 28 | ## Index 29 | 30 | ### External modules 31 | 32 | * ["combinators"](modules/_combinators_.md) 33 | * ["decoder"](modules/_decoder_.md) 34 | * ["index"](modules/_index_.md) 35 | * ["result"](modules/_result_.md) 36 | 37 | --- 38 | 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mojotech/json-type-validation", 3 | "version": "3.1.0", 4 | "description": "runtime type checking and validation of untyped JSON data", 5 | "keywords": [ 6 | "TypeScript", 7 | "JSON" 8 | ], 9 | "main": "dist/index.js", 10 | "module": "dist/index.es5.js", 11 | "typings": "dist/types/index.d.ts", 12 | "files": [ 13 | "dist" 14 | ], 15 | "author": "Elias Mulhall ", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/mojotech/json-type-validation" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/mojotech/json-type-validation/issues" 22 | }, 23 | "homepage": "https://github.com/mojotech/json-type-validation", 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">=6.0.0" 27 | }, 28 | "scripts": { 29 | "lint": "tslint -t codeFrame --project tsconfig-test.json", 30 | "prebuild": "rimraf dist", 31 | "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", 32 | "start": "rollup -c rollup.config.ts -w", 33 | "test": "jest", 34 | "test:watch": "jest --watch", 35 | "test:prod": "npm run lint && npm run test -- --coverage --no-cache", 36 | "typecheck": "tsc --lib es2015 --noEmit --strict test/**.ts", 37 | "typecheck:watch": "tsc -w --lib es2015 --noEmit --strict test/**.ts" 38 | }, 39 | "jest": { 40 | "transform": { 41 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" 42 | }, 43 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 44 | "moduleFileExtensions": [ 45 | "ts", 46 | "tsx", 47 | "js" 48 | ], 49 | "coveragePathIgnorePatterns": [ 50 | "/node_modules/", 51 | "/test/" 52 | ], 53 | "coverageThreshold": { 54 | "global": { 55 | "branches": 90, 56 | "functions": 95, 57 | "lines": 95, 58 | "statements": 95 59 | } 60 | }, 61 | "collectCoverage": true 62 | }, 63 | "devDependencies": { 64 | "@types/jest": "^22.2.3", 65 | "@types/node": "^10.5.3", 66 | "@types/lodash": "^4.5.0", 67 | "colors": "^1.1.2", 68 | "cross-env": "^5.0.1", 69 | "jest": "^22.0.2", 70 | "prettier": "^1.4.4", 71 | "rimraf": "^2.6.1", 72 | "rollup": "^0.53.0", 73 | "rollup-plugin-commonjs": "^8.0.2", 74 | "rollup-plugin-node-resolve": "^3.0.0", 75 | "rollup-plugin-sourcemaps": "^0.4.2", 76 | "rollup-plugin-typescript2": "^0.9.0", 77 | "ts-jest": "^22.0.0", 78 | "ts-node": "^4.1.0", 79 | "tslint": "^5.11.0", 80 | "tslint-config-prettier": "^1.14.0", 81 | "tslint-config-standard": "^7.1.0", 82 | "typedoc": "^0.13.0", 83 | "typedoc-plugin-markdown": "^1.0.13", 84 | "typescript": "~3.1.0" 85 | }, 86 | "dependencies": { 87 | "lodash": "^4.5.0" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/phone-example.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Decoder, 3 | string, 4 | number, 5 | constant, 6 | object, 7 | array, 8 | optional, 9 | oneOf, 10 | union 11 | } from '../src/index'; 12 | 13 | describe('decode phone number objects', () => { 14 | enum PhoneUse { 15 | Mobile = 'Mobile', 16 | Home = 'Home', 17 | Work = 'Work' 18 | } 19 | 20 | interface PhoneNumber { 21 | id: number; 22 | use?: PhoneUse; 23 | } 24 | 25 | interface InternationalPhone extends PhoneNumber { 26 | international: true; 27 | rawNumber: string; 28 | } 29 | 30 | interface DomesticPhone extends PhoneNumber { 31 | international: false; 32 | areaCode: string; 33 | prefix: string; 34 | lineNumber: string; 35 | } 36 | 37 | type Phone = DomesticPhone | InternationalPhone; 38 | 39 | const phoneUseDecoder: Decoder = oneOf( 40 | constant(PhoneUse.Mobile), 41 | constant(PhoneUse.Home), 42 | constant(PhoneUse.Work) 43 | ); 44 | 45 | const internationalPhoneDecoder: Decoder = object({ 46 | id: number(), 47 | use: optional(phoneUseDecoder), 48 | international: constant(true), 49 | rawNumber: string() 50 | }); 51 | 52 | const domesticPhoneDecoder: Decoder = object({ 53 | id: number(), 54 | use: optional(phoneUseDecoder), 55 | international: constant(false), 56 | areaCode: string(), 57 | prefix: string(), 58 | lineNumber: string() 59 | }); 60 | 61 | const phoneDecoder: Decoder = union(domesticPhoneDecoder, internationalPhoneDecoder); 62 | 63 | const phonesDecoder: Decoder = array(phoneDecoder); 64 | 65 | it('can decode both international and domestic phones', () => { 66 | const json = [ 67 | { 68 | id: 1, 69 | use: 'Work', 70 | international: false, 71 | areaCode: '123', 72 | prefix: '456', 73 | lineNumber: '7890' 74 | }, 75 | { 76 | id: 2, 77 | use: 'Work', 78 | international: true, 79 | rawNumber: '111234567890' 80 | }, 81 | { 82 | id: 3, 83 | international: false, 84 | areaCode: '000', 85 | prefix: '000', 86 | lineNumber: '5555' 87 | } 88 | ]; 89 | 90 | expect(phonesDecoder.run(json)).toEqual({ok: true, result: json}); 91 | }); 92 | 93 | it('fails when an object is neither an international or domestic phone', () => { 94 | const json = [ 95 | { 96 | id: 1, 97 | use: 'Work', 98 | international: false, 99 | areaCode: '123', 100 | prefix: '456', 101 | lineNumber: '7890' 102 | }, 103 | { 104 | id: 5 105 | } 106 | ]; 107 | 108 | const error = phonesDecoder.run(json); 109 | expect(error).toMatchObject({ 110 | ok: false, 111 | error: { 112 | at: 'input[1]', 113 | message: [ 114 | 'expected a value matching one of the decoders, got the errors ', 115 | `["at error: the key 'international' is required but was not present", `, 116 | `"at error: the key 'international' is required but was not present"]` 117 | ].join('') 118 | } 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /src/result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The result of a computation that may fail. The decoding function 3 | * `Decoder.run` returns a `Result`. The value of a `Result` is either `Ok` if 4 | * the computation succeeded, or `Err` if there was some failure in the 5 | * process. 6 | */ 7 | export type Result = Ok | Err; 8 | 9 | /** 10 | * The success type variant for `Result`. Denotes that a result value was 11 | * computed with no errors. 12 | */ 13 | export interface Ok { 14 | ok: true; 15 | result: V; 16 | } 17 | 18 | /** 19 | * The error type variant for `Result`. Denotes that some error occurred before 20 | * the result was computed. 21 | */ 22 | export interface Err { 23 | ok: false; 24 | error: E; 25 | } 26 | 27 | /** 28 | * Wraps values in an `Ok` type. 29 | * 30 | * Example: `ok(5) // => {ok: true, result: 5}` 31 | */ 32 | export const ok = (result: V): Ok => ({ok: true, result: result}); 33 | 34 | /** 35 | * Typeguard for `Ok`. 36 | */ 37 | export const isOk = (r: Result): r is Ok => r.ok === true; 38 | 39 | /** 40 | * Wraps errors in an `Err` type. 41 | * 42 | * Example: `err('on fire') // => {ok: false, error: 'on fire'}` 43 | */ 44 | export const err = (error: E): Err => ({ok: false, error: error}); 45 | 46 | /** 47 | * Typeguard for `Err`. 48 | */ 49 | export const isErr = (r: Result): r is Err => r.ok === false; 50 | 51 | /** 52 | * Create a `Promise` that either resolves with the result of `Ok` or rejects 53 | * with the error of `Err`. 54 | */ 55 | export const asPromise = (r: Result): Promise => 56 | r.ok === true ? Promise.resolve(r.result) : Promise.reject(r.error); 57 | 58 | /** 59 | * Unwraps a `Result` and returns either the result of an `Ok`, or 60 | * `defaultValue`. 61 | * 62 | * Example: 63 | * ``` 64 | * Result.withDefault(5, number().run(json)) 65 | * ``` 66 | * 67 | * It would be nice if `Decoder` had an instance method that mirrored this 68 | * function. Such a method would look something like this: 69 | * ``` 70 | * class Decoder { 71 | * runWithDefault = (defaultValue: A, json: any): A => 72 | * Result.withDefault(defaultValue, this.run(json)); 73 | * } 74 | * 75 | * number().runWithDefault(5, json) 76 | * ``` 77 | * Unfortunately, the type of `defaultValue: A` on the method causes issues 78 | * with type inference on the `object` decoder in some situations. While these 79 | * inference issues can be solved by providing the optional type argument for 80 | * `object`s, the extra trouble and confusion doesn't seem worth it. 81 | */ 82 | export const withDefault = (defaultValue: V, r: Result): V => 83 | r.ok === true ? r.result : defaultValue; 84 | 85 | /** 86 | * Return the successful result, or throw an error. 87 | */ 88 | export const withException = (r: Result): V => { 89 | if (r.ok === true) { 90 | return r.result; 91 | } else { 92 | throw r.error; 93 | } 94 | }; 95 | 96 | /** 97 | * Given an array of `Result`s, return the successful values. 98 | */ 99 | export const successes = (results: Result[]): A[] => 100 | results.reduce((acc: A[], r: Result) => (r.ok === true ? acc.concat(r.result) : acc), []); 101 | 102 | /** 103 | * Apply `f` to the result of an `Ok`, or pass the error through. 104 | */ 105 | export const map = (f: (value: A) => B, r: Result): Result => 106 | r.ok === true ? ok(f(r.result)) : r; 107 | 108 | /** 109 | * Apply `f` to the result of two `Ok`s, or pass an error through. If both 110 | * `Result`s are errors then the first one is returned. 111 | */ 112 | export const map2 = (f: (av: A, bv: B) => C, ar: Result, br: Result): Result => 113 | ar.ok === false ? ar : 114 | br.ok === false ? br : 115 | ok(f(ar.result, br.result)); 116 | 117 | /** 118 | * Apply `f` to the error of an `Err`, or pass the success through. 119 | */ 120 | export const mapError = (f: (error: A) => B, r: Result): Result => 121 | r.ok === true ? r : err(f(r.error)); 122 | 123 | /** 124 | * Chain together a sequence of computations that may fail, similar to a 125 | * `Promise`. If the first computation fails then the error will propagate 126 | * through. If it succeeds, then `f` will be applied to the value, returning a 127 | * new `Result`. 128 | */ 129 | export const andThen = (f: (value: A) => Result, r: Result): Result => 130 | r.ok === true ? f(r.result) : r; 131 | -------------------------------------------------------------------------------- /docs/modules/_combinators_.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["combinators"](../modules/_combinators_.md) 2 | 3 | # External module: "combinators" 4 | 5 | ## Index 6 | 7 | ### Variables 8 | 9 | * [anyJson](_combinators_.md#anyjson) 10 | * [array](_combinators_.md#array) 11 | * [boolean](_combinators_.md#boolean) 12 | * [constant](_combinators_.md#constant) 13 | * [dict](_combinators_.md#dict) 14 | * [fail](_combinators_.md#fail) 15 | * [intersection](_combinators_.md#intersection) 16 | * [lazy](_combinators_.md#lazy) 17 | * [number](_combinators_.md#number) 18 | * [object](_combinators_.md#object) 19 | * [oneOf](_combinators_.md#oneof) 20 | * [optional](_combinators_.md#optional) 21 | * [string](_combinators_.md#string) 22 | * [succeed](_combinators_.md#succeed) 23 | * [tuple](_combinators_.md#tuple) 24 | * [union](_combinators_.md#union) 25 | * [unknownJson](_combinators_.md#unknownjson) 26 | * [valueAt](_combinators_.md#valueat) 27 | * [withDefault](_combinators_.md#withdefault) 28 | 29 | --- 30 | 31 | ## Variables 32 | 33 | 34 | 35 | ### `` anyJson 36 | 37 | **● anyJson**: *[anyJson]()* = Decoder.anyJson 38 | 39 | See `Decoder.anyJson` 40 | 41 | ___ 42 | 43 | 44 | ### `` array 45 | 46 | **● array**: *[array](../classes/_decoder_.decoder.md#array)* = Decoder.array 47 | 48 | See `Decoder.array` 49 | 50 | ___ 51 | 52 | 53 | ### `` boolean 54 | 55 | **● boolean**: *[boolean](../classes/_decoder_.decoder.md#boolean)* = Decoder.boolean 56 | 57 | See `Decoder.boolean` 58 | 59 | ___ 60 | 61 | 62 | ### `` constant 63 | 64 | **● constant**: *[constant](../classes/_decoder_.decoder.md#constant)* = Decoder.constant 65 | 66 | See `Decoder.constant` 67 | 68 | ___ 69 | 70 | 71 | ### `` dict 72 | 73 | **● dict**: *[dict]()* = Decoder.dict 74 | 75 | See `Decoder.dict` 76 | 77 | ___ 78 | 79 | 80 | ### `` fail 81 | 82 | **● fail**: *[fail]()* = Decoder.fail 83 | 84 | See `Decoder.fail` 85 | 86 | ___ 87 | 88 | 89 | ### `` intersection 90 | 91 | **● intersection**: *[intersection](../classes/_decoder_.decoder.md#intersection)* = Decoder.intersection 92 | 93 | See `Decoder.intersection` 94 | 95 | ___ 96 | 97 | 98 | ### `` lazy 99 | 100 | **● lazy**: *[lazy]()* = Decoder.lazy 101 | 102 | See `Decoder.lazy` 103 | 104 | ___ 105 | 106 | 107 | ### `` number 108 | 109 | **● number**: *[number](../classes/_decoder_.decoder.md#number)* = Decoder.number 110 | 111 | See `Decoder.number` 112 | 113 | ___ 114 | 115 | 116 | ### `` object 117 | 118 | **● object**: *[object](../classes/_decoder_.decoder.md#object)* = Decoder.object 119 | 120 | See `Decoder.object` 121 | 122 | ___ 123 | 124 | 125 | ### `` oneOf 126 | 127 | **● oneOf**: *[oneOf]()* = Decoder.oneOf 128 | 129 | See `Decoder.oneOf` 130 | 131 | ___ 132 | 133 | 134 | ### `` optional 135 | 136 | **● optional**: *[optional]()* = Decoder.optional 137 | 138 | See `Decoder.optional` 139 | 140 | ___ 141 | 142 | 143 | ### `` string 144 | 145 | **● string**: *[string](../classes/_decoder_.decoder.md#string)* = Decoder.string 146 | 147 | See `Decoder.string` 148 | 149 | ___ 150 | 151 | 152 | ### `` succeed 153 | 154 | **● succeed**: *[succeed]()* = Decoder.succeed 155 | 156 | See `Decoder.succeed` 157 | 158 | ___ 159 | 160 | 161 | ### `` tuple 162 | 163 | **● tuple**: *[tuple](../classes/_decoder_.decoder.md#tuple)* = Decoder.tuple 164 | 165 | See `Decoder.tuple` 166 | 167 | ___ 168 | 169 | 170 | ### `` union 171 | 172 | **● union**: *[union](../classes/_decoder_.decoder.md#union)* = Decoder.union 173 | 174 | See `Decoder.union` 175 | 176 | ___ 177 | 178 | 179 | ### `` unknownJson 180 | 181 | **● unknownJson**: *`function`* = Decoder.unknownJson 182 | 183 | See `Decoder.unknownJson` 184 | 185 | #### Type declaration 186 | ▸(): [Decoder](../classes/_decoder_.decoder.md)<`unknown`> 187 | 188 | **Returns:** [Decoder](../classes/_decoder_.decoder.md)<`unknown`> 189 | 190 | ___ 191 | 192 | 193 | ### `` valueAt 194 | 195 | **● valueAt**: *[valueAt]()* = Decoder.valueAt 196 | 197 | See `Decoder.valueAt` 198 | 199 | ___ 200 | 201 | 202 | ### `` withDefault 203 | 204 | **● withDefault**: *[withDefault]()* = Decoder.withDefault 205 | 206 | See `Decoder.withDefault` 207 | 208 | ___ 209 | 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON Type Validation 2 | 3 | A [TypeScript](https://www.typescriptlang.org/) library to perform type checking and validation on untyped JSON data at runtime. 4 | 5 | This library owes thanks to: 6 | 7 | - [JsonDecoder](https://github.com/aische/JsonDecoder) by Daniel van den Eijkel 8 | - [Type-safe JSON Decoder](https://github.com/ooesili/type-safe-json-decoder) by Wesley Merkel 9 | - The Elm [Json.Decode](http://package.elm-lang.org/packages/elm-lang/core/latest/Json-Decode) API 10 | 11 | ## Installation 12 | ``` 13 | npm i @mojotech/json-type-validation 14 | ``` 15 | Projects using `< typescript@3.0.1` will need a polyfill for the `unknown` 16 | type, such as [unknown-ts](https://www.npmjs.com/package/unknown-ts). 17 | 18 | ## Motivation 19 | 20 | Let's say we're creating a web app for our pet sitting business, and we've 21 | picked TypeScript as one of our core technologies. This is a great choice 22 | because the extra stability and type safety that TypeScript provides is really 23 | going to help us market our business. 24 | 25 | We've defined the data we need to track about each client's pet: 26 | 27 | ```typescript 28 | interface Pet { 29 | name: string; 30 | species: string; 31 | age?: number; 32 | isCute?: boolean; 33 | } 34 | ``` 35 | 36 | And we've got some data about current client's pets which is stored as JSON: 37 | 38 | ```typescript 39 | const croc: Pet = JSON.parse('{"name":"Lyle","species":"Crocodile","isCute":true}') 40 | const moose: Pet = JSON.parse('{"name":"Bullwinkle","age":59}') 41 | ``` 42 | 43 | But that can't be right -- our data for `moose` is missing information required 44 | for the `Pet` interface, but TypeScript compiles the code just fine! 45 | 46 | Of course this isn't an issue with TypeScript, but with our own type 47 | annotations. In TypeScript `JSON.parse` has a return type of `any`, which pushes 48 | the responsibility of verifying the type of data onto the user. By assuming that 49 | all of our data is correctly formed, we've left ourselves open to unexpected 50 | errors at runtime. 51 | 52 | Unfortunately TypeScript doesn't provide a good built-in way to deal with this 53 | issue. Providing run-time type information is one of TypeScript's 54 | [non-goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals), 55 | and our web app is too important to risk using a forked version of TypeScript 56 | with that added functionality. 57 | [Type guards](https://basarat.gitbooks.io/typescript/docs/types/typeGuard.html) 58 | work, but are limited in that they circumvent type inference instead of working 59 | with it, and can be cumbersome to write. 60 | 61 | With `json-type-validation` we can define decoders that validate untyped json 62 | input. Decoders are concise, composable, and typecheck against our defined types 63 | and interfaces. 64 | 65 | ```typescript 66 | import {Decoder, object, string, optional, number, boolean} from '@mojotech/json-type-validation' 67 | 68 | const petDecoder: Decoder = object({ 69 | name: string(), 70 | species: string(), 71 | age: optional(number()), 72 | isCute: optional(boolean()) 73 | }) 74 | ``` 75 | 76 | Finally, we can choose from a number of decoding methods to validate json and 77 | report success or failure. When some json input fails validation the decoder 78 | clearly shows how the data was malformed. 79 | 80 | ```typescript 81 | const lyle: Pet = petDecoder.runWithException(croc) 82 | 83 | const bullwinkle: Pet = petDecoder.runWithException(moose) 84 | // Throws the exception: 85 | // `Input: {"name":"Bullwinkle","age":59} 86 | // Failed at input: the key 'species' is required but was not present` 87 | ``` 88 | 89 | ## Documentation 90 | 91 | [Documentation](https://github.com/mojotech/json-type-validation/tree/master/docs). 92 | 93 | ## Building 94 | 95 | ### With Nix 96 | 97 | There exists some [Nix](https://nixos.org/nix) infrastructure that can be used 98 | to reproduce a build environment exactly. A helper shell script lives at 99 | `bin/jtv` that you can use to enter environments for multiple uses. 100 | You'll need to follow the directions on the Nix website to install and use the 101 | Nix package manager. 102 | 103 | * To enter a shell suitable for building the library run `./bin/jtv 104 | build-shell`. This will leave you in the root of the project and automatically 105 | install any project and npm dependencies. You can run further yarn commands 106 | here. 107 | * To build the library for distribution and exit you can run `./bin/jtv distribute`. 108 | * To enter a build shell and run the build process, watching for changes, run 109 | `./bin/jtv build-watch`. 110 | * To run an arbitrary command in a build environment use `./bin/jtv run 111 | COMMAND`. For example, `./bin/jtv run yarn test` will run the tests and exit. 112 | -------------------------------------------------------------------------------- /docs/modules/_result_.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["result"](../modules/_result_.md) 2 | 3 | # External module: "result" 4 | 5 | ## Index 6 | 7 | ### Interfaces 8 | 9 | * [Err](../interfaces/_result_.err.md) 10 | * [Ok](../interfaces/_result_.ok.md) 11 | 12 | ### Type aliases 13 | 14 | * [Result](_result_.md#result) 15 | 16 | ### Functions 17 | 18 | * [andThen](_result_.md#andthen) 19 | * [asPromise](_result_.md#aspromise) 20 | * [err](_result_.md#err-1) 21 | * [isErr](_result_.md#iserr) 22 | * [isOk](_result_.md#isok) 23 | * [map](_result_.md#map) 24 | * [map2](_result_.md#map2) 25 | * [mapError](_result_.md#maperror) 26 | * [ok](_result_.md#ok-1) 27 | * [successes](_result_.md#successes) 28 | * [withDefault](_result_.md#withdefault) 29 | * [withException](_result_.md#withexception) 30 | 31 | --- 32 | 33 | ## Type aliases 34 | 35 | 36 | 37 | ### Result 38 | 39 | **ΤResult**: * [Ok](../interfaces/_result_.ok.md)<`V`> | [Err](../interfaces/_result_.err.md)<`E`> 40 | * 41 | 42 | 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. 43 | 44 | ___ 45 | 46 | ## Functions 47 | 48 | 49 | 50 | ### `` andThen 51 | 52 | ▸ **andThen**A,B,E(f: *`function`*, r: *[Result](_result_.md#result)<`A`, `E`>*): [Result](_result_.md#result)<`B`, `E`> 53 | 54 | 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`. 55 | 56 | **Type parameters:** 57 | 58 | #### A 59 | #### B 60 | #### E 61 | **Parameters:** 62 | 63 | | Param | Type | 64 | | ------ | ------ | 65 | | f | `function` | 66 | | r | [Result](_result_.md#result)<`A`, `E`> | 67 | 68 | **Returns:** [Result](_result_.md#result)<`B`, `E`> 69 | 70 | ___ 71 | 72 | 73 | ### `` asPromise 74 | 75 | ▸ **asPromise**V(r: *[Result](_result_.md#result)<`V`, `any`>*): `Promise`<`V`> 76 | 77 | Create a `Promise` that either resolves with the result of `Ok` or rejects with the error of `Err`. 78 | 79 | **Type parameters:** 80 | 81 | #### V 82 | **Parameters:** 83 | 84 | | Param | Type | 85 | | ------ | ------ | 86 | | r | [Result](_result_.md#result)<`V`, `any`> | 87 | 88 | **Returns:** `Promise`<`V`> 89 | 90 | ___ 91 | 92 | 93 | ### `` err 94 | 95 | ▸ **err**E(error: *`E`*): [Err](../interfaces/_result_.err.md)<`E`> 96 | 97 | Wraps errors in an `Err` type. 98 | 99 | Example: `err('on fire') // => {ok: false, error: 'on fire'}` 100 | 101 | **Type parameters:** 102 | 103 | #### E 104 | **Parameters:** 105 | 106 | | Param | Type | 107 | | ------ | ------ | 108 | | error | `E` | 109 | 110 | **Returns:** [Err](../interfaces/_result_.err.md)<`E`> 111 | 112 | ___ 113 | 114 | 115 | ### `` isErr 116 | 117 | ▸ **isErr**E(r: *[Result](_result_.md#result)<`any`, `E`>*): `boolean` 118 | 119 | Typeguard for `Err`. 120 | 121 | **Type parameters:** 122 | 123 | #### E 124 | **Parameters:** 125 | 126 | | Param | Type | 127 | | ------ | ------ | 128 | | r | [Result](_result_.md#result)<`any`, `E`> | 129 | 130 | **Returns:** `boolean` 131 | 132 | ___ 133 | 134 | 135 | ### `` isOk 136 | 137 | ▸ **isOk**V(r: *[Result](_result_.md#result)<`V`, `any`>*): `boolean` 138 | 139 | Typeguard for `Ok`. 140 | 141 | **Type parameters:** 142 | 143 | #### V 144 | **Parameters:** 145 | 146 | | Param | Type | 147 | | ------ | ------ | 148 | | r | [Result](_result_.md#result)<`V`, `any`> | 149 | 150 | **Returns:** `boolean` 151 | 152 | ___ 153 | 154 | 155 | ### `` map 156 | 157 | ▸ **map**A,B,E(f: *`function`*, r: *[Result](_result_.md#result)<`A`, `E`>*): [Result](_result_.md#result)<`B`, `E`> 158 | 159 | Apply `f` to the result of an `Ok`, or pass the error through. 160 | 161 | **Type parameters:** 162 | 163 | #### A 164 | #### B 165 | #### E 166 | **Parameters:** 167 | 168 | | Param | Type | 169 | | ------ | ------ | 170 | | f | `function` | 171 | | r | [Result](_result_.md#result)<`A`, `E`> | 172 | 173 | **Returns:** [Result](_result_.md#result)<`B`, `E`> 174 | 175 | ___ 176 | 177 | 178 | ### `` map2 179 | 180 | ▸ **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`> 181 | 182 | 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. 183 | 184 | **Type parameters:** 185 | 186 | #### A 187 | #### B 188 | #### C 189 | #### E 190 | **Parameters:** 191 | 192 | | Param | Type | 193 | | ------ | ------ | 194 | | f | `function` | 195 | | ar | [Result](_result_.md#result)<`A`, `E`> | 196 | | br | [Result](_result_.md#result)<`B`, `E`> | 197 | 198 | **Returns:** [Result](_result_.md#result)<`C`, `E`> 199 | 200 | ___ 201 | 202 | 203 | ### `` mapError 204 | 205 | ▸ **mapError**V,A,B(f: *`function`*, r: *[Result](_result_.md#result)<`V`, `A`>*): [Result](_result_.md#result)<`V`, `B`> 206 | 207 | Apply `f` to the error of an `Err`, or pass the success through. 208 | 209 | **Type parameters:** 210 | 211 | #### V 212 | #### A 213 | #### B 214 | **Parameters:** 215 | 216 | | Param | Type | 217 | | ------ | ------ | 218 | | f | `function` | 219 | | r | [Result](_result_.md#result)<`V`, `A`> | 220 | 221 | **Returns:** [Result](_result_.md#result)<`V`, `B`> 222 | 223 | ___ 224 | 225 | 226 | ### `` ok 227 | 228 | ▸ **ok**V(result: *`V`*): [Ok](../interfaces/_result_.ok.md)<`V`> 229 | 230 | Wraps values in an `Ok` type. 231 | 232 | Example: `ok(5) // => {ok: true, result: 5}` 233 | 234 | **Type parameters:** 235 | 236 | #### V 237 | **Parameters:** 238 | 239 | | Param | Type | 240 | | ------ | ------ | 241 | | result | `V` | 242 | 243 | **Returns:** [Ok](../interfaces/_result_.ok.md)<`V`> 244 | 245 | ___ 246 | 247 | 248 | ### `` successes 249 | 250 | ▸ **successes**A(results: *[Result](_result_.md#result)<`A`, `any`>[]*): `A`[] 251 | 252 | Given an array of `Result`s, return the successful values. 253 | 254 | **Type parameters:** 255 | 256 | #### A 257 | **Parameters:** 258 | 259 | | Param | Type | 260 | | ------ | ------ | 261 | | results | [Result](_result_.md#result)<`A`, `any`>[] | 262 | 263 | **Returns:** `A`[] 264 | 265 | ___ 266 | 267 | 268 | ### `` withDefault 269 | 270 | ▸ **withDefault**V(defaultValue: *`V`*, r: *[Result](_result_.md#result)<`V`, `any`>*): `V` 271 | 272 | Unwraps a `Result` and returns either the result of an `Ok`, or `defaultValue`. 273 | 274 | Example: 275 | 276 | ``` 277 | Result.withDefault(5, number().run(json)) 278 | ``` 279 | 280 | It would be nice if `Decoder` had an instance method that mirrored this function. Such a method would look something like this: 281 | 282 | ``` 283 | class Decoder { 284 | runWithDefault = (defaultValue: A, json: any): A => 285 | Result.withDefault(defaultValue, this.run(json)); 286 | } 287 | 288 | number().runWithDefault(5, json) 289 | ``` 290 | 291 | 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. 292 | 293 | **Type parameters:** 294 | 295 | #### V 296 | **Parameters:** 297 | 298 | | Param | Type | 299 | | ------ | ------ | 300 | | defaultValue | `V` | 301 | | r | [Result](_result_.md#result)<`V`, `any`> | 302 | 303 | **Returns:** `V` 304 | 305 | ___ 306 | 307 | 308 | ### `` withException 309 | 310 | ▸ **withException**V(r: *[Result](_result_.md#result)<`V`, `any`>*): `V` 311 | 312 | Return the successful result, or throw an error. 313 | 314 | **Type parameters:** 315 | 316 | #### V 317 | **Parameters:** 318 | 319 | | Param | Type | 320 | | ------ | ------ | 321 | | r | [Result](_result_.md#result)<`V`, `any`> | 322 | 323 | **Returns:** `V` 324 | 325 | ___ 326 | 327 | -------------------------------------------------------------------------------- /src/decoder.ts: -------------------------------------------------------------------------------- 1 | import * as Result from './result'; 2 | import isEqual from "lodash/isEqual" 3 | 4 | /** 5 | * Information describing how json data failed to match a decoder. 6 | * Includes the full input json, since in most cases it's useless to know how a 7 | * decoder failed without also seeing the malformed data. 8 | */ 9 | export interface DecoderError { 10 | kind: 'DecoderError'; 11 | input: unknown; 12 | at: string; 13 | message: string; 14 | } 15 | 16 | /** 17 | * Alias for the result of the `Decoder.run` method. On success returns `Ok` 18 | * with the decoded value of type `A`, on failure returns `Err` containing a 19 | * `DecoderError`. 20 | */ 21 | type RunResult = Result.Result; 22 | 23 | /** 24 | * Alias for the result of the internal `Decoder.decode` method. Since `decode` 25 | * is a private function it returns a partial decoder error on failure, which 26 | * will be completed and polished when handed off to the `run` method. 27 | */ 28 | type DecodeResult = Result.Result>; 29 | 30 | /** 31 | * Defines a mapped type over an interface `A`. `DecoderObject` is an 32 | * interface that has all the keys or `A`, but each key's property type is 33 | * mapped to a decoder for that type. This type is used when creating decoders 34 | * for objects. 35 | * 36 | * Example: 37 | * ``` 38 | * interface X { 39 | * a: boolean; 40 | * b: string; 41 | * } 42 | * 43 | * const decoderObject: DecoderObject = { 44 | * a: boolean(), 45 | * b: string() 46 | * } 47 | * ``` 48 | */ 49 | export type DecoderObject = {[t in keyof A]: Decoder}; 50 | 51 | /** 52 | * Type guard for `DecoderError`. One use case of the type guard is in the 53 | * `catch` of a promise. Typescript types the error argument of `catch` as 54 | * `any`, so when dealing with a decoder as a promise you may need to 55 | * distinguish between a `DecoderError` and an error string. 56 | */ 57 | export const isDecoderError = (a: any): a is DecoderError => 58 | a.kind === 'DecoderError' && typeof a.at === 'string' && typeof a.message === 'string'; 59 | 60 | /* 61 | * Helpers 62 | */ 63 | const isJsonArray = (json: any): json is unknown[] => Array.isArray(json); 64 | 65 | const isJsonObject = (json: any): json is Record => 66 | typeof json === 'object' && json !== null && !isJsonArray(json); 67 | 68 | const typeString = (json: unknown): string => { 69 | switch (typeof json) { 70 | case 'string': 71 | return 'a string'; 72 | case 'number': 73 | return 'a number'; 74 | case 'boolean': 75 | return 'a boolean'; 76 | case 'undefined': 77 | return 'undefined'; 78 | case 'object': 79 | if (json instanceof Array) { 80 | return 'an array'; 81 | } else if (json === null) { 82 | return 'null'; 83 | } else { 84 | return 'an object'; 85 | } 86 | default: 87 | return JSON.stringify(json); 88 | } 89 | }; 90 | 91 | const expectedGot = (expected: string, got: unknown) => 92 | `expected ${expected}, got ${typeString(got)}`; 93 | 94 | const printPath = (paths: (string | number)[]): string => 95 | paths.map(path => (typeof path === 'string' ? `.${path}` : `[${path}]`)).join(''); 96 | 97 | const prependAt = (newAt: string, {at, ...rest}: Partial): Partial => ({ 98 | at: newAt + (at || ''), 99 | ...rest 100 | }); 101 | 102 | /** 103 | * Decoders transform json objects with unknown structure into known and 104 | * verified forms. You can create objects of type `Decoder` with either the 105 | * primitive decoder functions, such as `boolean()` and `string()`, or by 106 | * applying higher-order decoders to the primitives, such as `array(boolean())` 107 | * or `dict(string())`. 108 | * 109 | * Each of the decoder functions are available both as a static method on 110 | * `Decoder` and as a function alias -- for example the string decoder is 111 | * defined at `Decoder.string()`, but is also aliased to `string()`. Using the 112 | * function aliases exported with the library is recommended. 113 | * 114 | * `Decoder` exposes a number of 'run' methods, which all decode json in the 115 | * same way, but communicate success and failure in different ways. The `map` 116 | * and `andThen` methods modify decoders without having to call a 'run' method. 117 | * 118 | * Alternatively, the main decoder `run()` method returns an object of type 119 | * `Result`. This library provides a number of helper 120 | * functions for dealing with the `Result` type, so you can do all the same 121 | * things with a `Result` as with the decoder methods. 122 | */ 123 | export class Decoder { 124 | /** 125 | * The Decoder class constructor is kept private to separate the internal 126 | * `decode` function from the external `run` function. The distinction 127 | * between the two functions is that `decode` returns a 128 | * `Partial` on failure, which contains an unfinished error 129 | * report. When `run` is called on a decoder, the relevant series of `decode` 130 | * calls is made, and then on failure the resulting `Partial` 131 | * is turned into a `DecoderError` by filling in the missing information. 132 | * 133 | * While hiding the constructor may seem restrictive, leveraging the 134 | * provided decoder combinators and helper functions such as 135 | * `andThen` and `map` should be enough to build specialized decoders as 136 | * needed. 137 | */ 138 | private constructor(private decode: (json: unknown) => DecodeResult) {} 139 | 140 | /** 141 | * Decoder primitive that validates strings, and fails on all other input. 142 | */ 143 | static string(): Decoder { 144 | return new Decoder( 145 | (json: unknown) => 146 | typeof json === 'string' 147 | ? Result.ok(json) 148 | : Result.err({message: expectedGot('a string', json)}) 149 | ); 150 | } 151 | 152 | /** 153 | * Decoder primitive that validates numbers, and fails on all other input. 154 | */ 155 | static number(): Decoder { 156 | return new Decoder( 157 | (json: unknown) => 158 | typeof json === 'number' 159 | ? Result.ok(json) 160 | : Result.err({message: expectedGot('a number', json)}) 161 | ); 162 | } 163 | 164 | /** 165 | * Decoder primitive that validates booleans, and fails on all other input. 166 | */ 167 | static boolean(): Decoder { 168 | return new Decoder( 169 | (json: unknown) => 170 | typeof json === 'boolean' 171 | ? Result.ok(json) 172 | : Result.err({message: expectedGot('a boolean', json)}) 173 | ); 174 | } 175 | 176 | /** 177 | * Escape hatch to bypass validation. Always succeeds and types the result as 178 | * `any`. Useful for defining decoders incrementally, particularly for 179 | * complex objects. 180 | * 181 | * Example: 182 | * ``` 183 | * interface User { 184 | * name: string; 185 | * complexUserData: ComplexType; 186 | * } 187 | * 188 | * const userDecoder: Decoder = object({ 189 | * name: string(), 190 | * complexUserData: anyJson() 191 | * }); 192 | * ``` 193 | */ 194 | static anyJson = (): Decoder => new Decoder((json: any) => Result.ok(json)); 195 | 196 | /** 197 | * Decoder identity function which always succeeds and types the result as 198 | * `unknown`. 199 | */ 200 | static unknownJson = (): Decoder => 201 | new Decoder((json: unknown) => Result.ok(json)); 202 | 203 | /** 204 | * Decoder primitive that only matches on exact values. 205 | * 206 | * For primitive values and shallow structures of primitive values `constant` 207 | * will infer an exact literal type: 208 | * ``` 209 | * | Decoder | Type | 210 | * | ---------------------------- | ------------------------------| 211 | * | constant(true) | Decoder | 212 | * | constant(false) | Decoder | 213 | * | constant(null) | Decoder | 214 | * | constant(undefined) | Decoder | 215 | * | constant('alaska') | Decoder<'alaska'> | 216 | * | constant(50) | Decoder<50> | 217 | * | constant([1,2,3]) | Decoder<[1,2,3]> | 218 | * | constant({x: 't'}) | Decoder<{x: 't'}> | 219 | * ``` 220 | * 221 | * Inference breaks on nested structures, which require an annotation to get 222 | * the literal type: 223 | * ``` 224 | * | Decoder | Type | 225 | * | -----------------------------|-------------------------------| 226 | * | constant([1,[2]]) | Decoder<(number|number[])[]> | 227 | * | constant<[1,[2]]>([1,[2]]) | Decoder<[1,[2]]> | 228 | * | constant({x: [1]}) | Decoder<{x: number[]}> | 229 | * | constant<{x: [1]}>({x: [1]}) | Decoder<{x: [1]}> | 230 | * ``` 231 | */ 232 | static constant(value: T): Decoder; 233 | static constant(value: U): Decoder; 234 | static constant>(value: U): Decoder; 235 | static constant(value: T): Decoder; 236 | static constant(value: any) { 237 | return new Decoder( 238 | (json: unknown) => 239 | isEqual(json, value) 240 | ? Result.ok(value) 241 | : Result.err({message: `expected ${JSON.stringify(value)}, got ${JSON.stringify(json)}`}) 242 | ); 243 | } 244 | 245 | /** 246 | * An higher-order decoder that runs decoders on specified fields of an object, 247 | * and returns a new object with those fields. If `object` is called with no 248 | * arguments, then the outer object part of the json is validated but not the 249 | * contents, typing the result as a record where all keys have a value of 250 | * type `unknown`. 251 | * 252 | * The `optional` and `constant` decoders are particularly useful for decoding 253 | * objects that match typescript interfaces. 254 | * 255 | * To decode a single field that is inside of an object see `valueAt`. 256 | * 257 | * Example: 258 | * ``` 259 | * object({x: number(), y: number()}).run({x: 5, y: 10}) 260 | * // => {ok: true, result: {x: 5, y: 10}} 261 | * 262 | * object().map(Object.keys).run({n: 1, i: [], c: {}, e: 'e'}) 263 | * // => {ok: true, result: ['n', 'i', 'c', 'e']} 264 | * ``` 265 | */ 266 | static object(): Decoder>; 267 | static object(decoders: DecoderObject): Decoder; 268 | static object(decoders?: DecoderObject) { 269 | return new Decoder((json: unknown) => { 270 | if (isJsonObject(json) && decoders) { 271 | let obj: any = {}; 272 | for (const key in decoders) { 273 | if (decoders.hasOwnProperty(key)) { 274 | const r = decoders[key].decode(json[key]); 275 | if (r.ok === true) { 276 | // tslint:disable-next-line:strict-type-predicates 277 | if (r.result !== undefined) { 278 | obj[key] = r.result; 279 | } 280 | } else if (json[key] === undefined) { 281 | return Result.err({message: `the key '${key}' is required but was not present`}); 282 | } else { 283 | return Result.err(prependAt(`.${key}`, r.error)); 284 | } 285 | } 286 | } 287 | return Result.ok(obj); 288 | } else if (isJsonObject(json)) { 289 | return Result.ok(json); 290 | } else { 291 | return Result.err({message: expectedGot('an object', json)}); 292 | } 293 | }); 294 | } 295 | 296 | /** 297 | * Decoder for json arrays. Runs `decoder` on each array element, and succeeds 298 | * if all elements are successfully decoded. If no `decoder` argument is 299 | * provided then the outer array part of the json is validated but not the 300 | * contents, typing the result as `unknown[]`. 301 | * 302 | * To decode a single value that is inside of an array see `valueAt`. 303 | * 304 | * Examples: 305 | * ``` 306 | * array(number()).run([1, 2, 3]) 307 | * // => {ok: true, result: [1, 2, 3]} 308 | * 309 | * array(array(boolean())).run([[true], [], [true, false, false]]) 310 | * // => {ok: true, result: [[true], [], [true, false, false]]} 311 | * 312 | * 313 | * const validNumbersDecoder = array() 314 | * .map((arr: unknown[]) => arr.map(number().run)) 315 | * .map(Result.successes) 316 | * 317 | * validNumbersDecoder.run([1, true, 2, 3, 'five', 4, []]) 318 | * // {ok: true, result: [1, 2, 3, 4]} 319 | * 320 | * validNumbersDecoder.run([false, 'hi', {}]) 321 | * // {ok: true, result: []} 322 | * 323 | * validNumbersDecoder.run(false) 324 | * // {ok: false, error: {..., message: "expected an array, got a boolean"}} 325 | * ``` 326 | */ 327 | static array(): Decoder; 328 | static array(decoder: Decoder): Decoder; 329 | static array(decoder?: Decoder) { 330 | return new Decoder(json => { 331 | if (isJsonArray(json) && decoder) { 332 | const decodeValue = (v: unknown, i: number): DecodeResult => 333 | Result.mapError(err => prependAt(`[${i}]`, err), decoder.decode(v)); 334 | 335 | return json.reduce( 336 | (acc: DecodeResult, v: unknown, i: number) => 337 | Result.map2((arr, result) => [...arr, result], acc, decodeValue(v, i)), 338 | Result.ok([]) 339 | ); 340 | } else if (isJsonArray(json)) { 341 | return Result.ok(json); 342 | } else { 343 | return Result.err({message: expectedGot('an array', json)}); 344 | } 345 | }); 346 | } 347 | 348 | /** 349 | * Decoder for fixed-length arrays, aka Tuples. 350 | * 351 | * Supports up to 8-tuples. 352 | * 353 | * Example: 354 | * ``` 355 | * tuple([number(), number(), string()]).run([5, 10, 'px']) 356 | * // => {ok: true, result: [5, 10, 'px']} 357 | * ``` 358 | */ 359 | static tuple(decoder: [Decoder]): Decoder<[A]>; 360 | static tuple(decoder: [Decoder, Decoder]): Decoder<[A, B]>; 361 | static tuple(decoder: [Decoder, Decoder, Decoder]): Decoder<[A, B, C]>; 362 | static tuple(decoder: [Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D]>; // prettier-ignore 363 | static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E]>; // prettier-ignore 364 | static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E, F]>; // prettier-ignore 365 | static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E, F, G]>; // prettier-ignore 366 | static tuple(decoder: [Decoder, Decoder, Decoder, Decoder, Decoder, Decoder, Decoder, Decoder]): Decoder<[A, B, C, D, E, F, G, H]>; // prettier-ignore 367 | static tuple(decoders: Decoder[]) { 368 | return new Decoder((json: unknown) => { 369 | if (isJsonArray(json)) { 370 | if (json.length !== decoders.length) { 371 | return Result.err({ 372 | message: `expected a tuple of length ${decoders.length}, got one of length ${ 373 | json.length 374 | }` 375 | }); 376 | } 377 | const result = []; 378 | for (let i: number = 0; i < decoders.length; i++) { 379 | const nth = decoders[i].decode(json[i]); 380 | if (nth.ok) { 381 | result[i] = nth.result; 382 | } else { 383 | return Result.err(prependAt(`[${i}]`, nth.error)); 384 | } 385 | } 386 | return Result.ok(result); 387 | } else { 388 | return Result.err({message: expectedGot(`a tuple of length ${decoders.length}`, json)}); 389 | } 390 | }); 391 | } 392 | 393 | /** 394 | * Decoder for json objects where the keys are unknown strings, but the values 395 | * should all be of the same type. 396 | * 397 | * Example: 398 | * ``` 399 | * dict(number()).run({chocolate: 12, vanilla: 10, mint: 37}); 400 | * // => {ok: true, result: {chocolate: 12, vanilla: 10, mint: 37}} 401 | * ``` 402 | */ 403 | static dict = (decoder: Decoder): Decoder> => 404 | new Decoder(json => { 405 | if (isJsonObject(json)) { 406 | let obj: Record = {}; 407 | for (const key in json) { 408 | if (json.hasOwnProperty(key)) { 409 | const r = decoder.decode(json[key]); 410 | if (r.ok === true) { 411 | obj[key] = r.result; 412 | } else { 413 | return Result.err(prependAt(`.${key}`, r.error)); 414 | } 415 | } 416 | } 417 | return Result.ok(obj); 418 | } else { 419 | return Result.err({message: expectedGot('an object', json)}); 420 | } 421 | }); 422 | 423 | /** 424 | * Decoder for values that may be `undefined`. This is primarily helpful for 425 | * decoding interfaces with optional fields. 426 | * 427 | * Example: 428 | * ``` 429 | * interface User { 430 | * id: number; 431 | * isOwner?: boolean; 432 | * } 433 | * 434 | * const decoder: Decoder = object({ 435 | * id: number(), 436 | * isOwner: optional(boolean()) 437 | * }); 438 | * ``` 439 | */ 440 | static optional = (decoder: Decoder): Decoder => 441 | new Decoder( 442 | (json: unknown) => (json === undefined ? Result.ok(undefined) : decoder.decode(json)) 443 | ); 444 | 445 | /** 446 | * Decoder that attempts to run each decoder in `decoders` and either succeeds 447 | * with the first successful decoder, or fails after all decoders have failed. 448 | * 449 | * Note that `oneOf` expects the decoders to all have the same return type, 450 | * while `union` creates a decoder for the union type of all the input 451 | * decoders. 452 | * 453 | * Examples: 454 | * ``` 455 | * oneOf(string(), number().map(String)) 456 | * oneOf(constant('start'), constant('stop'), succeed('unknown')) 457 | * ``` 458 | */ 459 | static oneOf = (...decoders: Decoder[]): Decoder => 460 | new Decoder((json: unknown) => { 461 | const errors: Partial[] = []; 462 | for (let i: number = 0; i < decoders.length; i++) { 463 | const r = decoders[i].decode(json); 464 | if (r.ok === true) { 465 | return r; 466 | } else { 467 | errors[i] = r.error; 468 | } 469 | } 470 | const errorsList = errors 471 | .map(error => `at error${error.at || ''}: ${error.message}`) 472 | .join('", "'); 473 | return Result.err({ 474 | message: `expected a value matching one of the decoders, got the errors ["${errorsList}"]` 475 | }); 476 | }); 477 | 478 | /** 479 | * Combines 2-8 decoders of disparate types into a decoder for the union of all 480 | * the types. 481 | * 482 | * If you need more than 8 variants for your union, it's possible to use 483 | * `oneOf` in place of `union` as long as you annotate every decoder with the 484 | * union type. 485 | * 486 | * Example: 487 | * ``` 488 | * type C = {a: string} | {b: number}; 489 | * 490 | * const unionDecoder: Decoder = union(object({a: string()}), object({b: number()})); 491 | * const oneOfDecoder: Decoder = oneOf(object({a: string()}), object({b: number()})); 492 | * ``` 493 | */ 494 | static union (ad: Decoder, bd: Decoder): Decoder; // prettier-ignore 495 | static union (ad: Decoder, bd: Decoder, cd: Decoder): Decoder; // prettier-ignore 496 | static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder): Decoder; // prettier-ignore 497 | static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder): Decoder; // prettier-ignore 498 | static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder): Decoder; // prettier-ignore 499 | static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder): Decoder; // prettier-ignore 500 | static union (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder, hd: Decoder): Decoder; // prettier-ignore 501 | static union(ad: Decoder, bd: Decoder, ...decoders: Decoder[]): Decoder { 502 | return Decoder.oneOf(ad, bd, ...decoders); 503 | } 504 | 505 | /** 506 | * Combines 2-8 object decoders into a decoder for the intersection of all the objects. 507 | * 508 | * Example: 509 | * ``` 510 | * interface Pet { 511 | * name: string; 512 | * maxLegs: number; 513 | * } 514 | * 515 | * interface Cat extends Pet { 516 | * evil: boolean; 517 | * } 518 | * 519 | * const petDecoder: Decoder = object({name: string(), maxLegs: number()}); 520 | * const catDecoder: Decoder = intersection(petDecoder, object({evil: boolean()})); 521 | * ``` 522 | */ 523 | static intersection (ad: Decoder, bd: Decoder): Decoder; // prettier-ignore 524 | static intersection (ad: Decoder, bd: Decoder, cd: Decoder): Decoder; // prettier-ignore 525 | static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder): Decoder; // prettier-ignore 526 | static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder): Decoder; // prettier-ignore 527 | static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder): Decoder; // prettier-ignore 528 | static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder): Decoder; // prettier-ignore 529 | static intersection (ad: Decoder, bd: Decoder, cd: Decoder, dd: Decoder, ed: Decoder, fd: Decoder, gd: Decoder, hd: Decoder): Decoder; // prettier-ignore 530 | static intersection(ad: Decoder, bd: Decoder, ...ds: Decoder[]): Decoder { 531 | return new Decoder((json: unknown) => 532 | [ad, bd, ...ds].reduce( 533 | (acc: DecodeResult, decoder) => Result.map2(Object.assign, acc, decoder.decode(json)), 534 | Result.ok({}) 535 | ) 536 | ); 537 | } 538 | 539 | /** 540 | * Decoder that always succeeds with either the decoded value, or a fallback 541 | * default value. 542 | */ 543 | static withDefault = (defaultValue: A, decoder: Decoder): Decoder => 544 | new Decoder((json: unknown) => 545 | Result.ok(Result.withDefault(defaultValue, decoder.decode(json))) 546 | ); 547 | 548 | /** 549 | * Decoder that pulls a specific field out of a json structure, instead of 550 | * decoding and returning the full structure. The `paths` array describes the 551 | * object keys and array indices to traverse, so that values can be pulled out 552 | * of a nested structure. 553 | * 554 | * Example: 555 | * ``` 556 | * const decoder = valueAt(['a', 'b', 0], string()); 557 | * 558 | * decoder.run({a: {b: ['surprise!']}}) 559 | * // => {ok: true, result: 'surprise!'} 560 | * 561 | * decoder.run({a: {x: 'cats'}}) 562 | * // => {ok: false, error: {... at: 'input.a.b[0]' message: 'path does not exist'}} 563 | * ``` 564 | * 565 | * Note that the `decoder` is ran on the value found at the last key in the 566 | * path, even if the last key is not found. This allows the `optional` 567 | * decoder to succeed when appropriate. 568 | * ``` 569 | * const optionalDecoder = valueAt(['a', 'b', 'c'], optional(string())); 570 | * 571 | * optionalDecoder.run({a: {b: {c: 'surprise!'}}}) 572 | * // => {ok: true, result: 'surprise!'} 573 | * 574 | * optionalDecoder.run({a: {b: 'cats'}}) 575 | * // => {ok: false, error: {... at: 'input.a.b.c' message: 'expected an object, got "cats"'} 576 | * 577 | * optionalDecoder.run({a: {b: {z: 1}}}) 578 | * // => {ok: true, result: undefined} 579 | * ``` 580 | */ 581 | static valueAt = (paths: (string | number)[], decoder: Decoder): Decoder => 582 | new Decoder((json: unknown) => { 583 | let jsonAtPath: any = json; 584 | for (let i: number = 0; i < paths.length; i++) { 585 | if (jsonAtPath === undefined) { 586 | return Result.err({ 587 | at: printPath(paths.slice(0, i + 1)), 588 | message: 'path does not exist' 589 | }); 590 | } else if (typeof paths[i] === 'string' && !isJsonObject(jsonAtPath)) { 591 | return Result.err({ 592 | at: printPath(paths.slice(0, i + 1)), 593 | message: expectedGot('an object', jsonAtPath) 594 | }); 595 | } else if (typeof paths[i] === 'number' && !isJsonArray(jsonAtPath)) { 596 | return Result.err({ 597 | at: printPath(paths.slice(0, i + 1)), 598 | message: expectedGot('an array', jsonAtPath) 599 | }); 600 | } else { 601 | jsonAtPath = jsonAtPath[paths[i]]; 602 | } 603 | } 604 | return Result.mapError( 605 | error => 606 | jsonAtPath === undefined 607 | ? {at: printPath(paths), message: 'path does not exist'} 608 | : prependAt(printPath(paths), error), 609 | decoder.decode(jsonAtPath) 610 | ); 611 | }); 612 | 613 | /** 614 | * Decoder that ignores the input json and always succeeds with `fixedValue`. 615 | */ 616 | static succeed = (fixedValue: A): Decoder => 617 | new Decoder((json: unknown) => Result.ok(fixedValue)); 618 | 619 | /** 620 | * Decoder that ignores the input json and always fails with `errorMessage`. 621 | */ 622 | static fail = (errorMessage: string): Decoder => 623 | new Decoder((json: unknown) => Result.err({message: errorMessage})); 624 | 625 | /** 626 | * Decoder that allows for validating recursive data structures. Unlike with 627 | * functions, decoders assigned to variables can't reference themselves 628 | * before they are fully defined. We can avoid prematurely referencing the 629 | * decoder by wrapping it in a function that won't be called until use, at 630 | * which point the decoder has been defined. 631 | * 632 | * Example: 633 | * ``` 634 | * interface Comment { 635 | * msg: string; 636 | * replies: Comment[]; 637 | * } 638 | * 639 | * const decoder: Decoder = object({ 640 | * msg: string(), 641 | * replies: lazy(() => array(decoder)) 642 | * }); 643 | * ``` 644 | */ 645 | static lazy = (mkDecoder: () => Decoder): Decoder => 646 | new Decoder((json: unknown) => mkDecoder().decode(json)); 647 | 648 | /** 649 | * Run the decoder and return a `Result` with either the decoded value or a 650 | * `DecoderError` containing the json input, the location of the error, and 651 | * the error message. 652 | * 653 | * Examples: 654 | * ``` 655 | * number().run(12) 656 | * // => {ok: true, result: 12} 657 | * 658 | * string().run(9001) 659 | * // => 660 | * // { 661 | * // ok: false, 662 | * // error: { 663 | * // kind: 'DecoderError', 664 | * // input: 9001, 665 | * // at: 'input', 666 | * // message: 'expected a string, got 9001' 667 | * // } 668 | * // } 669 | * ``` 670 | */ 671 | run = (json: unknown): RunResult => 672 | Result.mapError( 673 | error => ({ 674 | kind: 'DecoderError' as 'DecoderError', 675 | input: json, 676 | at: 'input' + (error.at || ''), 677 | message: error.message || '' 678 | }), 679 | this.decode(json) 680 | ); 681 | 682 | /** 683 | * Run the decoder as a `Promise`. 684 | */ 685 | runPromise = (json: unknown): Promise => Result.asPromise(this.run(json)); 686 | 687 | /** 688 | * Run the decoder and return the value on success, or throw an exception 689 | * with a formatted error string. 690 | */ 691 | runWithException = (json: unknown): A => Result.withException(this.run(json)); 692 | 693 | /** 694 | * Construct a new decoder that applies a transformation to the decoded 695 | * result. If the decoder succeeds then `f` will be applied to the value. If 696 | * it fails the error will propagated through. 697 | * 698 | * Example: 699 | * ``` 700 | * number().map(x => x * 5).run(10) 701 | * // => {ok: true, result: 50} 702 | * ``` 703 | */ 704 | map = (f: (value: A) => B): Decoder => 705 | new Decoder((json: unknown) => Result.map(f, this.decode(json))); 706 | 707 | /** 708 | * Chain together a sequence of decoders. The first decoder will run, and 709 | * then the function will determine what decoder to run second. If the result 710 | * of the first decoder succeeds then `f` will be applied to the decoded 711 | * value. If it fails the error will propagate through. 712 | * 713 | * This is a very powerful method -- it can act as both the `map` and `where` 714 | * methods, can improve error messages for edge cases, and can be used to 715 | * make a decoder for custom types. 716 | * 717 | * Example of adding an error message: 718 | * ``` 719 | * const versionDecoder = valueAt(['version'], number()); 720 | * const infoDecoder3 = object({a: boolean()}); 721 | * 722 | * const decoder = versionDecoder.andThen(version => { 723 | * switch (version) { 724 | * case 3: 725 | * return infoDecoder3; 726 | * default: 727 | * return fail(`Unable to decode info, version ${version} is not supported.`); 728 | * } 729 | * }); 730 | * 731 | * decoder.run({version: 3, a: true}) 732 | * // => {ok: true, result: {a: true}} 733 | * 734 | * decoder.run({version: 5, x: 'abc'}) 735 | * // => 736 | * // { 737 | * // ok: false, 738 | * // error: {... message: 'Unable to decode info, version 5 is not supported.'} 739 | * // } 740 | * ``` 741 | * 742 | * Example of decoding a custom type: 743 | * ``` 744 | * // nominal type for arrays with a length of at least one 745 | * type NonEmptyArray = T[] & { __nonEmptyArrayBrand__: void }; 746 | * 747 | * const nonEmptyArrayDecoder = (values: Decoder): Decoder> => 748 | * array(values).andThen(arr => 749 | * arr.length > 0 750 | * ? succeed(createNonEmptyArray(arr)) 751 | * : fail(`expected a non-empty array, got an empty array`) 752 | * ); 753 | * ``` 754 | */ 755 | andThen = (f: (value: A) => Decoder): Decoder => 756 | new Decoder((json: unknown) => 757 | Result.andThen(value => f(value).decode(json), this.decode(json)) 758 | ); 759 | 760 | /** 761 | * Add constraints to a decoder _without_ changing the resulting type. The 762 | * `test` argument is a predicate function which returns true for valid 763 | * inputs. When `test` fails on an input, the decoder fails with the given 764 | * `errorMessage`. 765 | * 766 | * ``` 767 | * const chars = (length: number): Decoder => 768 | * string().where( 769 | * (s: string) => s.length === length, 770 | * `expected a string of length ${length}` 771 | * ); 772 | * 773 | * chars(5).run('12345') 774 | * // => {ok: true, result: '12345'} 775 | * 776 | * chars(2).run('HELLO') 777 | * // => {ok: false, error: {... message: 'expected a string of length 2'}} 778 | * 779 | * chars(12).run(true) 780 | * // => {ok: false, error: {... message: 'expected a string, got a boolean'}} 781 | * ``` 782 | */ 783 | where = (test: (value: A) => boolean, errorMessage: string): Decoder => 784 | this.andThen((value: A) => (test(value) ? Decoder.succeed(value) : Decoder.fail(errorMessage))); 785 | } 786 | -------------------------------------------------------------------------------- /test/json-decode.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Decoder, 3 | Result, 4 | isDecoderError, 5 | string, 6 | number, 7 | boolean, 8 | anyJson, 9 | unknownJson, 10 | constant, 11 | object, 12 | array, 13 | dict, 14 | optional, 15 | oneOf, 16 | union, 17 | intersection, 18 | withDefault, 19 | valueAt, 20 | succeed, 21 | tuple, 22 | fail, 23 | lazy 24 | } from '../src/index'; 25 | 26 | describe('string', () => { 27 | const decoder = string(); 28 | 29 | it('succeeds when given a string', () => { 30 | expect(decoder.run('hey')).toEqual({ok: true, result: 'hey'}); 31 | }); 32 | 33 | it('fails when given a number', () => { 34 | expect(decoder.run(1)).toMatchObject({ 35 | ok: false, 36 | error: {at: 'input', message: 'expected a string, got a number'} 37 | }); 38 | }); 39 | 40 | it('fails when given null', () => { 41 | expect(decoder.run(null)).toMatchObject({ 42 | ok: false, 43 | error: {at: 'input', message: 'expected a string, got null'} 44 | }); 45 | }); 46 | 47 | it('fails when given a boolean', () => { 48 | expect(decoder.run(true)).toMatchObject({ 49 | ok: false, 50 | error: {at: 'input', message: 'expected a string, got a boolean'} 51 | }); 52 | }); 53 | }); 54 | 55 | describe('number', () => { 56 | const decoder = number(); 57 | 58 | it('succeeds when given a number', () => { 59 | expect(decoder.run(5)).toEqual({ok: true, result: 5}); 60 | }); 61 | 62 | it('fails when given a string', () => { 63 | expect(decoder.run('hey')).toMatchObject({ 64 | ok: false, 65 | error: {at: 'input', message: 'expected a number, got a string'} 66 | }); 67 | }); 68 | 69 | it('fails when given boolean', () => { 70 | expect(decoder.run(true)).toMatchObject({ 71 | ok: false, 72 | error: {at: 'input', message: 'expected a number, got a boolean'} 73 | }); 74 | }); 75 | }); 76 | 77 | describe('boolean', () => { 78 | const decoder = boolean(); 79 | 80 | it('succeeds when given a boolean', () => { 81 | expect(decoder.run(true)).toEqual({ok: true, result: true}); 82 | }); 83 | 84 | it('fails when given a string', () => { 85 | expect(decoder.run('hey')).toMatchObject({ 86 | ok: false, 87 | error: {at: 'input', message: 'expected a boolean, got a string'} 88 | }); 89 | }); 90 | 91 | it('fails when given a number', () => { 92 | expect(decoder.run(1)).toMatchObject({ 93 | ok: false, 94 | error: {at: 'input', message: 'expected a boolean, got a number'} 95 | }); 96 | }); 97 | }); 98 | 99 | describe('anyJson', () => { 100 | it('bypasses type validation', () => { 101 | // in a real use case this could be a deeply nested object 102 | type ComplexType = number; 103 | 104 | interface User { 105 | name: string; 106 | complexUserData: ComplexType; 107 | } 108 | 109 | const userDecoder: Decoder = object({ 110 | name: string(), 111 | complexUserData: anyJson() 112 | }); 113 | 114 | expect(userDecoder.run({name: 'Wanda', complexUserData: true})).toEqual({ 115 | ok: true, 116 | result: {name: 'Wanda', complexUserData: true} 117 | }); 118 | 119 | expect(userDecoder.run({name: 'Willard', complexUserData: 'trash data'})).toEqual({ 120 | ok: true, 121 | result: {name: 'Willard', complexUserData: 'trash data'} 122 | }); 123 | 124 | expect(userDecoder.run({name: 73, complexUserData: []})).toMatchObject({ 125 | ok: false, 126 | error: {at: 'input.name', message: 'expected a string, got a number'} 127 | }); 128 | }); 129 | }); 130 | 131 | describe('unknownJson', () => { 132 | it('accepts any values', () => { 133 | expect(unknownJson().run(1)).toEqual({ok: true, result: 1}); 134 | expect(unknownJson().run(false)).toEqual({ok: true, result: false}); 135 | expect(unknownJson().run({boots: 'n cats'})).toEqual({ok: true, result: {boots: 'n cats'}}); 136 | }); 137 | }); 138 | 139 | describe('constant', () => { 140 | it('works for string-literals', () => { 141 | const decoder: Decoder<'zero'> = constant('zero'); 142 | 143 | expect(decoder.run('zero')).toEqual({ok: true, result: 'zero'}); 144 | }); 145 | 146 | it('fails when given two different values', () => { 147 | const decoder: Decoder<42> = constant(42); 148 | 149 | expect(decoder.run(true)).toMatchObject({ 150 | ok: false, 151 | error: {at: 'input', message: 'expected 42, got true'} 152 | }); 153 | }); 154 | 155 | it('can decode the true-literal type', () => { 156 | interface TrueValue { 157 | x: true; 158 | } 159 | const decoder: Decoder = object({x: constant(true)}); 160 | 161 | expect(decoder.run({x: true})).toEqual({ok: true, result: {x: true}}); 162 | }); 163 | 164 | it('can decode the false-literal type', () => { 165 | interface FalseValue { 166 | x: false; 167 | } 168 | const decoder: Decoder = object({x: constant(false)}); 169 | 170 | expect(decoder.run({x: false})).toEqual({ok: true, result: {x: false}}); 171 | }); 172 | 173 | it('can decode the null-literal type', () => { 174 | interface NullValue { 175 | x: null; 176 | } 177 | const decoder: Decoder = object({x: constant(null)}); 178 | 179 | expect(decoder.run({x: null})).toEqual({ok: true, result: {x: null}}); 180 | }); 181 | 182 | it('can decode a constant array', () => { 183 | const decoder: Decoder<[1, 2, 3]> = constant([1, 2, 3]); 184 | 185 | expect(decoder.run([1, 2, 3])).toEqual({ok: true, result: [1, 2, 3]}); 186 | expect(decoder.run([1, 2, 3, 4])).toMatchObject({ 187 | ok: false, 188 | error: {at: 'input', message: 'expected [1,2,3], got [1,2,3,4]'} 189 | }); 190 | }); 191 | 192 | it('can decode a constant object', () => { 193 | const decoder: Decoder<{a: true; b: 12}> = constant({a: true, b: 12}); 194 | 195 | expect(decoder.run({a: true, b: 12})).toEqual({ok: true, result: {a: true, b: 12}}); 196 | expect(decoder.run({a: true, b: 7})).toMatchObject({ 197 | ok: false, 198 | error: {at: 'input', message: 'expected {"a":true,"b":12}, got {"a":true,"b":7}'} 199 | }); 200 | }); 201 | }); 202 | 203 | describe('object', () => { 204 | describe('when given valid JSON', () => { 205 | it('can decode a simple object', () => { 206 | const decoder = object({x: number()}); 207 | 208 | expect(decoder.run({x: 5})).toMatchObject({ok: true, result: {x: 5}}); 209 | }); 210 | 211 | it('can decode a nested object', () => { 212 | const decoder = object({ 213 | payload: object({x: number(), y: number()}), 214 | error: constant(false) 215 | }); 216 | const json = {payload: {x: 5, y: 2}, error: false}; 217 | 218 | expect(decoder.run(json)).toEqual({ok: true, result: json}); 219 | }); 220 | }); 221 | 222 | describe('when given incorrect JSON', () => { 223 | it('fails when not given an object', () => { 224 | const decoder = object({x: number()}); 225 | 226 | expect(decoder.run('true')).toMatchObject({ 227 | ok: false, 228 | error: {at: 'input', message: 'expected an object, got a string'} 229 | }); 230 | }); 231 | 232 | it('fails when given an array', () => { 233 | const decoder = object({x: number()}); 234 | 235 | expect(decoder.run([])).toMatchObject({ 236 | ok: false, 237 | error: {at: 'input', message: 'expected an object, got an array'} 238 | }); 239 | }); 240 | 241 | it('reports a missing key', () => { 242 | const decoder = object({x: number()}); 243 | 244 | expect(decoder.run({})).toMatchObject({ 245 | ok: false, 246 | error: {at: 'input', message: "the key 'x' is required but was not present"} 247 | }); 248 | }); 249 | 250 | it('reports invalid values', () => { 251 | const decoder = object({name: string()}); 252 | 253 | expect(decoder.run({name: 5})).toMatchObject({ 254 | ok: false, 255 | error: {at: 'input.name', message: 'expected a string, got a number'} 256 | }); 257 | }); 258 | 259 | it('properly displays nested errors', () => { 260 | const decoder = object({ 261 | hello: object({ 262 | hey: object({ 263 | 'Howdy!': string() 264 | }) 265 | }) 266 | }); 267 | 268 | const error = decoder.run({hello: {hey: {'Howdy!': {}}}}); 269 | expect(error).toMatchObject({ 270 | ok: false, 271 | error: {at: 'input.hello.hey.Howdy!', message: 'expected a string, got an object'} 272 | }); 273 | }); 274 | }); 275 | 276 | it('ignores optional fields that decode to undefined', () => { 277 | const decoder = object({ 278 | a: number(), 279 | b: optional(string()) 280 | }); 281 | 282 | expect(decoder.run({a: 12, b: 'hats'})).toEqual({ok: true, result: {a: 12, b: 'hats'}}); 283 | expect(decoder.run({a: 12})).toEqual({ok: true, result: {a: 12}}); 284 | }); 285 | 286 | it('decodes any object when the object shape is not specified', () => { 287 | const objectKeysDecoder: Decoder = object().map(Object.keys); 288 | 289 | expect(objectKeysDecoder.run({n: 1, i: [], c: {}, e: 'e'})).toEqual({ 290 | ok: true, 291 | result: ['n', 'i', 'c', 'e'] 292 | }); 293 | }); 294 | }); 295 | 296 | describe('array', () => { 297 | const decoder = array(number()); 298 | 299 | it('works when given an array', () => { 300 | expect(decoder.run([1, 2, 3])).toEqual({ok: true, result: [1, 2, 3]}); 301 | }); 302 | 303 | it('fails when given something other than a array', () => { 304 | expect(decoder.run('oops')).toMatchObject({ 305 | ok: false, 306 | error: {at: 'input', message: 'expected an array, got a string'} 307 | }); 308 | }); 309 | 310 | describe('when given something other than an array', () => { 311 | it('fails when the elements are of the wrong type', () => { 312 | expect(decoder.run(['dang'])).toMatchObject({ 313 | ok: false, 314 | error: {at: 'input[0]', message: 'expected a number, got a string'} 315 | }); 316 | }); 317 | 318 | it('properly displays nested errors', () => { 319 | const nestedDecoder = array(array(array(number()))); 320 | 321 | expect(nestedDecoder.run([[], [], [[1, 2, 3, false]]])).toMatchObject({ 322 | ok: false, 323 | error: {at: 'input[2][0][3]', message: 'expected a number, got a boolean'} 324 | }); 325 | }); 326 | }); 327 | 328 | it('decodes any array when the array members decoder is not specified', () => { 329 | const validNumbersDecoder = array() 330 | .map((arr: unknown[]) => arr.map(number().run)) 331 | .map(Result.successes); 332 | 333 | expect(validNumbersDecoder.run([1, true, 2, 3, 'five', 4, []])).toEqual({ 334 | ok: true, 335 | result: [1, 2, 3, 4] 336 | }); 337 | 338 | expect(validNumbersDecoder.run([false, 'hi', {}])).toEqual({ok: true, result: []}); 339 | 340 | expect(validNumbersDecoder.run(false)).toMatchObject({ 341 | ok: false, 342 | error: {message: 'expected an array, got a boolean'} 343 | }); 344 | }); 345 | }); 346 | 347 | describe('tuple', () => { 348 | describe('when given valid JSON', () => { 349 | it('can decode a simple tuple', () => { 350 | const decoder: Decoder<[number, number]> = tuple([number(), number()]); 351 | 352 | expect(decoder.run([5, 6])).toMatchObject({ok: true, result: [5, 6]}); 353 | }); 354 | 355 | it('can decode tuples of mixed types', () => { 356 | const decoder: Decoder<[number, string]> = tuple([number(), string()]); 357 | 358 | expect(decoder.run([1, 'a'])).toMatchObject({ok: true, result: [1, 'a']}); 359 | }); 360 | 361 | it('can decode a nested object', () => { 362 | const decoder: Decoder<[{x: number; y: number}, false]> = tuple([ 363 | object({x: number(), y: number()}), 364 | constant(false) 365 | ]); 366 | const json = [{x: 5, y: 2}, false]; 367 | 368 | expect(decoder.run(json)).toEqual({ok: true, result: json}); 369 | }); 370 | }); 371 | 372 | describe('when given incorrect JSON', () => { 373 | it('fails when the array length does not match', () => { 374 | const decoder: Decoder<[number]> = tuple([number()]); 375 | 376 | expect(decoder.run([1, 2])).toMatchObject({ 377 | ok: false, 378 | error: {at: 'input', message: 'expected a tuple of length 1, got one of length 2'} 379 | }); 380 | }); 381 | 382 | it('fails when given an object', () => { 383 | const decoder: Decoder<[number]> = tuple([number()]); 384 | 385 | expect(decoder.run({x: 1})).toMatchObject({ 386 | ok: false, 387 | error: {at: 'input', message: 'expected a tuple of length 1, got an object'} 388 | }); 389 | }); 390 | 391 | it('reports invalid values', () => { 392 | const decoder: Decoder<[number, string]> = tuple([number(), string()]); 393 | 394 | expect(decoder.run([4, 5])).toMatchObject({ 395 | ok: false, 396 | error: {at: 'input[1]', message: 'expected a string, got a number'} 397 | }); 398 | }); 399 | 400 | it('properly displays nested errors', () => { 401 | const decoder: Decoder<[{hey: {'Howdy!': string}}]> = tuple([ 402 | object({ 403 | hey: object({ 404 | 'Howdy!': string() 405 | }) 406 | }) 407 | ]); 408 | 409 | const error = decoder.run([{hey: {'Howdy!': {}}}]); 410 | expect(error).toMatchObject({ 411 | ok: false, 412 | error: {at: 'input[0].hey.Howdy!', message: 'expected a string, got an object'} 413 | }); 414 | }); 415 | }); 416 | }); 417 | 418 | describe('dict', () => { 419 | describe('with a simple value decoder', () => { 420 | const decoder = dict(number()); 421 | 422 | it('can decode an empty object', () => { 423 | expect(decoder.run({})).toEqual({ok: true, result: {}}); 424 | }); 425 | 426 | it('can decode an object of with arbitrary keys', () => { 427 | expect(decoder.run({a: 1, b: 2})).toEqual({ok: true, result: {a: 1, b: 2}}); 428 | }); 429 | 430 | it('fails if a value cannot be decoded', () => { 431 | expect(decoder.run({oh: 'no'})).toMatchObject({ 432 | ok: false, 433 | error: {at: 'input.oh', message: 'expected a number, got a string'} 434 | }); 435 | }); 436 | 437 | it('fails if given an array', () => { 438 | expect(decoder.run([])).toMatchObject({ 439 | ok: false, 440 | error: {at: 'input', message: 'expected an object, got an array'} 441 | }); 442 | }); 443 | 444 | it('fails if given a primitive', () => { 445 | expect(decoder.run(5)).toMatchObject({ 446 | ok: false, 447 | error: {at: 'input', message: 'expected an object, got a number'} 448 | }); 449 | }); 450 | }); 451 | 452 | describe('given a transformative value decoder', () => { 453 | const decoder = dict(string().map(str => str + '!')); 454 | 455 | it('transforms the values', () => { 456 | expect(decoder.run({hey: 'there', yo: 'dude'})).toEqual({ 457 | ok: true, 458 | result: {hey: 'there!', yo: 'dude!'} 459 | }); 460 | }); 461 | }); 462 | }); 463 | 464 | describe('optional', () => { 465 | describe('decoding a non-object type', () => { 466 | const decoder = optional(number()); 467 | 468 | it('can decode the given type', () => { 469 | expect(decoder.run(5)).toEqual({ok: true, result: 5}); 470 | }); 471 | 472 | it('can decode undefined', () => { 473 | expect(decoder.run(undefined)).toEqual({ok: true, result: undefined}); 474 | }); 475 | 476 | it('fails when the value is invalid', () => { 477 | expect(decoder.run(false)).toMatchObject({ 478 | ok: false, 479 | error: {at: 'input', message: 'expected a number, got a boolean'} 480 | }); 481 | }); 482 | }); 483 | 484 | describe('decoding an interface with optional fields', () => { 485 | interface User { 486 | id: number; 487 | isDog?: boolean; 488 | } 489 | 490 | const decoder: Decoder = object({ 491 | id: number(), 492 | isDog: optional(boolean()) 493 | }); 494 | 495 | it('can decode the object when the optional field is present', () => { 496 | expect(decoder.run({id: 1, isDog: true})).toEqual({ok: true, result: {id: 1, isDog: true}}); 497 | }); 498 | 499 | it('can decode the object when the optional field is missing', () => { 500 | expect(decoder.run({id: 2})).toEqual({ok: true, result: {id: 2}}); 501 | }); 502 | 503 | it('fails when the optional field is invalid', () => { 504 | const error = decoder.run({id: 3, isDog: 'supdog'}); 505 | expect(error).toMatchObject({ 506 | ok: false, 507 | error: {at: 'input.isDog', message: 'expected a boolean, got a string'} 508 | }); 509 | }); 510 | }); 511 | }); 512 | 513 | describe('oneOf', () => { 514 | describe('when given valid input', () => { 515 | it('can decode a value with a single alternative', () => { 516 | const decoder = oneOf(string()); 517 | 518 | expect(decoder.run('yo')).toEqual({ok: true, result: 'yo'}); 519 | }); 520 | 521 | it('can decode a value with multiple alternatives', () => { 522 | const decoder = array(oneOf(string().map(s => s.length), number())); 523 | 524 | expect(decoder.run(['hey', 10])).toEqual({ok: true, result: [3, 10]}); 525 | }); 526 | }); 527 | 528 | it('fails when a value does not match any decoder', () => { 529 | const decoder = oneOf(string(), number().map(String)); 530 | 531 | expect(decoder.run([])).toMatchObject({ 532 | ok: false, 533 | error: { 534 | at: 'input', 535 | message: 536 | 'expected a value matching one of the decoders, got the errors ' + 537 | '["at error: expected a string, got an array", "at error: expected a number, got an array"]' 538 | } 539 | }); 540 | }); 541 | 542 | it('fails and reports errors for nested values', () => { 543 | const decoder = array( 544 | oneOf(valueAt([1, 'a', 'b'], number()), valueAt([1, 'a', 'x'], number())) 545 | ); 546 | 547 | expect(decoder.run([[{}, {a: {b: true}}]])).toMatchObject({ 548 | ok: false, 549 | error: { 550 | at: 'input[0]', 551 | message: 552 | 'expected a value matching one of the decoders, got the errors ' + 553 | '["at error[1].a.b: expected a number, got a boolean", ' + 554 | '"at error[1].a.x: path does not exist"]' 555 | } 556 | }); 557 | }); 558 | 559 | it('can act as the union function when given the correct annotation', () => { 560 | type C = {a: string} | {b: number}; 561 | 562 | const decoder: Decoder = oneOf(object({a: string()}), object({b: number()})); 563 | 564 | expect(decoder.run({a: 'xyz'})).toEqual({ok: true, result: {a: 'xyz'}}); 565 | }); 566 | }); 567 | 568 | describe('union', () => { 569 | interface A { 570 | kind: 'a'; 571 | value: number; 572 | } 573 | interface B { 574 | kind: 'b'; 575 | value: boolean; 576 | } 577 | type C = A | B; 578 | 579 | const decoder: Decoder = union( 580 | object({kind: constant('a'), value: number()}), 581 | object({kind: constant('b'), value: boolean()}) 582 | ); 583 | 584 | it('can decode a value that matches one of the union types', () => { 585 | const json = {kind: 'a', value: 12}; 586 | expect(decoder.run(json)).toEqual({ok: true, result: json}); 587 | }); 588 | 589 | it('fails when a value does not match any decoders', () => { 590 | const error = decoder.run({kind: 'b', value: 12}); 591 | expect(error).toMatchObject({ 592 | ok: false, 593 | error: { 594 | at: 'input', 595 | message: 596 | 'expected a value matching one of the decoders, got the errors ' + 597 | '["at error.kind: expected "a", got "b"", "at error.value: expected a boolean, got a number"]' 598 | } 599 | }); 600 | }); 601 | }); 602 | 603 | describe('intersection', () => { 604 | it('uses two decoders to decode an extended interface', () => { 605 | interface A { 606 | a: number; 607 | } 608 | 609 | interface AB extends A { 610 | b: string; 611 | } 612 | 613 | const aDecoder: Decoder = object({a: number()}); 614 | const abDecoder: Decoder = intersection(aDecoder, object({b: string()})); 615 | 616 | expect(abDecoder.run({a: 12, b: '!!!'})).toEqual({ok: true, result: {a: 12, b: '!!!'}}); 617 | }); 618 | 619 | it('can combine many decoders', () => { 620 | interface UVWXYZ { 621 | u: true; 622 | v: string[]; 623 | w: boolean | null; 624 | x: number; 625 | y: string; 626 | z: boolean; 627 | } 628 | 629 | const uvwxyzDecoder: Decoder = intersection( 630 | object({u: constant(true)}), 631 | object({v: array(string())}), 632 | object({w: union(boolean(), constant(null))}), 633 | object({x: number()}), 634 | object({y: string(), z: boolean()}) 635 | ); 636 | 637 | expect(uvwxyzDecoder.run({u: true, v: [], w: null, x: 4, y: 'y', z: false})).toEqual({ 638 | ok: true, 639 | result: {u: true, v: [], w: null, x: 4, y: 'y', z: false} 640 | }); 641 | }); 642 | }); 643 | 644 | describe('withDefault', () => { 645 | const decoder = withDefault('puppies', string()); 646 | 647 | it('uses the json value when decoding is successful', () => { 648 | expect(decoder.run('pancakes')).toEqual({ok: true, result: 'pancakes'}); 649 | }); 650 | 651 | it('uses the default when the decoder fails', () => { 652 | expect(decoder.run(5)).toEqual({ok: true, result: 'puppies'}); 653 | }); 654 | }); 655 | 656 | describe('valueAt', () => { 657 | describe('decode an value', () => { 658 | it('can decode a single object field', () => { 659 | const decoder = valueAt(['a'], string()); 660 | expect(decoder.run({a: 'boots', b: 'cats'})).toEqual({ok: true, result: 'boots'}); 661 | }); 662 | 663 | it('can decode a single array value', () => { 664 | const decoder = valueAt([1], string()); 665 | expect(decoder.run(['boots', 'cats'])).toEqual({ok: true, result: 'cats'}); 666 | }); 667 | }); 668 | 669 | describe('decode a nested path', () => { 670 | const decoder = valueAt(['a', 1, 'b'], string()); 671 | 672 | it('can decode a field in a nested structure', () => { 673 | expect(decoder.run({a: [{}, {b: 'surprise!'}]})).toEqual({ok: true, result: 'surprise!'}); 674 | }); 675 | 676 | it('fails when an array path does not exist', () => { 677 | expect(decoder.run({a: []})).toMatchObject({ 678 | ok: false, 679 | error: {at: 'input.a[1].b', message: 'path does not exist'} 680 | }); 681 | }); 682 | 683 | it('fails when an object path does not exist', () => { 684 | expect(decoder.run({x: 12})).toMatchObject({ 685 | ok: false, 686 | error: {at: 'input.a[1]', message: 'path does not exist'} 687 | }); 688 | }); 689 | 690 | it('fails when the decoder fails at the end of the path', () => { 691 | expect(decoder.run({a: ['a', {b: 12}]})).toMatchObject({ 692 | ok: false, 693 | error: {at: 'input.a[1].b', message: 'expected a string, got a number'} 694 | }); 695 | }); 696 | }); 697 | 698 | describe('decode an optional field', () => { 699 | const decoder = valueAt(['a', 'b', 'c'], optional(string())); 700 | 701 | it('fails when the path does not exist', () => { 702 | const error = decoder.run({a: {x: 'cats'}}); 703 | expect(error).toMatchObject({ 704 | ok: false, 705 | error: {at: 'input.a.b.c', message: 'path does not exist'} 706 | }); 707 | }); 708 | 709 | it('succeeds when the final field is not found', () => { 710 | expect(decoder.run({a: {b: {z: 1}}})).toEqual({ok: true, result: undefined}); 711 | }); 712 | }); 713 | 714 | describe('non-object json', () => { 715 | it('only accepts json objects and arrays', () => { 716 | const decoder = valueAt(['a'], string()); 717 | 718 | expect(decoder.run('abc')).toMatchObject({ 719 | ok: false, 720 | error: {at: 'input.a', message: 'expected an object, got a string'} 721 | }); 722 | expect(decoder.run(true)).toMatchObject({ 723 | ok: false, 724 | error: {at: 'input.a', message: 'expected an object, got a boolean'} 725 | }); 726 | }); 727 | 728 | it('fails when a feild in the path does not correspond to a json object', () => { 729 | const decoder = valueAt(['a', 'b', 'c'], string()); 730 | 731 | const error = decoder.run({a: {b: 1}}); 732 | expect(error).toMatchObject({ 733 | ok: false, 734 | error: {at: 'input.a.b.c', message: 'expected an object, got a number'} 735 | }); 736 | }); 737 | 738 | it('fails when an index in the path does not correspond to a json array', () => { 739 | const decoder = valueAt([0, 0, 1], string()); 740 | 741 | const error = decoder.run([[false]]); 742 | expect(error).toMatchObject({ 743 | ok: false, 744 | error: {at: 'input[0][0][1]', message: 'expected an array, got a boolean'} 745 | }); 746 | }); 747 | }); 748 | 749 | it('decodes the input when given an empty path', () => { 750 | const decoder = valueAt([], number()); 751 | 752 | expect(decoder.run(12)).toEqual({ok: true, result: 12}); 753 | }); 754 | }); 755 | 756 | describe('succeed', () => { 757 | const decoder = succeed(12345); 758 | 759 | it('always decodes the input as the same value', () => { 760 | expect(decoder.run('pancakes')).toEqual({ok: true, result: 12345}); 761 | expect(decoder.run(5)).toEqual({ok: true, result: 12345}); 762 | }); 763 | }); 764 | 765 | describe('fail', () => { 766 | const wisdom = 'People don’t think it be like it is, but it do.'; 767 | const decoder = fail(wisdom); 768 | 769 | it('always fails and returns the same error message', () => { 770 | expect(decoder.run('pancakes')).toMatchObject({ 771 | ok: false, 772 | error: {at: 'input', message: wisdom} 773 | }); 774 | expect(decoder.run(5)).toMatchObject({ok: false, error: {at: 'input', message: wisdom}}); 775 | }); 776 | }); 777 | 778 | describe('lazy', () => { 779 | describe('decoding a primitive data type', () => { 780 | const decoder = lazy(() => string()); 781 | 782 | it('can decode type as normal', () => { 783 | expect(decoder.run('hello')).toEqual({ok: true, result: 'hello'}); 784 | }); 785 | 786 | it('does not alter the error message', () => { 787 | expect(decoder.run(5)).toMatchObject({ 788 | ok: false, 789 | error: {at: 'input', message: 'expected a string, got a number'} 790 | }); 791 | }); 792 | }); 793 | 794 | describe('decoding a recursive data structure', () => { 795 | interface Comment { 796 | msg: string; 797 | replies: Comment[]; 798 | } 799 | 800 | const decoder: Decoder = object({ 801 | msg: string(), 802 | replies: lazy(() => array(decoder)) 803 | }); 804 | 805 | it('can decode the data structure', () => { 806 | const tree = {msg: 'hey', replies: [{msg: 'hi', replies: []}]}; 807 | 808 | expect(decoder.run(tree)).toEqual({ 809 | ok: true, 810 | result: {msg: 'hey', replies: [{msg: 'hi', replies: []}]} 811 | }); 812 | }); 813 | 814 | it('fails when a nested value is invalid', () => { 815 | const badTree = {msg: 'hey', replies: [{msg: 'hi', replies: ['hello']}]}; 816 | 817 | expect(decoder.run(badTree)).toMatchObject({ 818 | ok: false, 819 | error: {at: 'input.replies[0].replies[0]', message: 'expected an object, got a string'} 820 | }); 821 | }); 822 | }); 823 | }); 824 | 825 | describe('runPromise', () => { 826 | const promise = (json: unknown): Promise => boolean().runPromise(json); 827 | 828 | it('resolves the promise when the decoder succeeds', () => { 829 | return expect(promise(true)).resolves.toBe(true); 830 | }); 831 | 832 | it('rejects the promise when the decoder fails', () => { 833 | return expect(promise(42)).rejects.toEqual({ 834 | kind: 'DecoderError', 835 | input: 42, 836 | at: 'input', 837 | message: 'expected a boolean, got a number' 838 | }); 839 | }); 840 | 841 | it('returns a DecoderError when the decoder fails', () => { 842 | return expect(promise(42).catch(e => isDecoderError(e))).resolves.toBeTruthy(); 843 | }); 844 | }); 845 | 846 | describe('runWithException', () => { 847 | const decoder = boolean(); 848 | 849 | it('can run a decoder and return the successful value', () => { 850 | expect(decoder.runWithException(false)).toBe(false); 851 | }); 852 | 853 | it('throws an exception when the decoder fails', () => { 854 | let thrownError: any; 855 | 856 | try { 857 | decoder.runWithException(42); 858 | } catch (e) { 859 | thrownError = e; 860 | } 861 | 862 | expect(thrownError).toEqual({ 863 | kind: 'DecoderError', 864 | input: 42, 865 | at: 'input', 866 | message: 'expected a boolean, got a number' 867 | }); 868 | }); 869 | }); 870 | 871 | describe('map', () => { 872 | it('can apply the identity function to the decoder', () => { 873 | const decoder = string().map(x => x); 874 | 875 | expect(decoder.run('hey there')).toEqual({ok: true, result: 'hey there'}); 876 | }); 877 | 878 | it('can apply an endomorphic function to the decoder', () => { 879 | const decoder = number().map(x => x * 5); 880 | 881 | expect(decoder.run(10)).toEqual({ok: true, result: 50}); 882 | }); 883 | 884 | it('can apply a function that transforms the type', () => { 885 | const decoder = string().map(x => x.length); 886 | 887 | expect(decoder.run('hey')).toEqual({ok: true, result: 3}); 888 | }); 889 | }); 890 | 891 | describe('andThen', () => { 892 | describe('creates decoders based on previous results', () => { 893 | const versionDecoder = valueAt(['version'], number()); 894 | const infoDecoder3 = object({a: boolean()}); 895 | 896 | const decoder = versionDecoder.andThen(version => { 897 | switch (version) { 898 | case 3: 899 | return infoDecoder3; 900 | default: 901 | return fail(`Unable to decode info, version ${version} is not supported.`); 902 | } 903 | }); 904 | 905 | it('can decode using both the first and second decoder', () => { 906 | expect(decoder.run({version: 5, x: 'bootsncats'})).toMatchObject({ 907 | ok: false, 908 | error: {at: 'input', message: 'Unable to decode info, version 5 is not supported.'} 909 | }); 910 | 911 | expect(decoder.run({version: 3, a: true})).toEqual({ok: true, result: {a: true}}); 912 | }); 913 | 914 | it('fails when the first decoder fails', () => { 915 | expect(decoder.run({version: null, a: true})).toMatchObject({ 916 | ok: false, 917 | error: {at: 'input.version', message: 'expected a number, got null'} 918 | }); 919 | }); 920 | 921 | it('fails when the second decoder fails', () => { 922 | const json = {version: 3, a: 1}; 923 | expect(decoder.run(json)).toMatchObject({ 924 | ok: false, 925 | error: {at: 'input.a', message: 'expected a boolean, got a number'} 926 | }); 927 | }); 928 | }); 929 | 930 | it('creates decoders for custom types', () => { 931 | type NonEmptyArray = T[] & {__nonEmptyArrayBrand__: void}; 932 | const createNonEmptyArray = (arr: T[]): NonEmptyArray => arr as NonEmptyArray; 933 | 934 | const nonEmptyArrayDecoder = (values: Decoder): Decoder> => 935 | array(values).andThen( 936 | arr => 937 | arr.length > 0 938 | ? succeed(createNonEmptyArray(arr)) 939 | : fail(`expected a non-empty array, got an empty array`) 940 | ); 941 | 942 | expect(nonEmptyArrayDecoder(number()).run([1, 2, 3])).toEqual({ 943 | ok: true, 944 | result: [1, 2, 3] 945 | }); 946 | 947 | expect(nonEmptyArrayDecoder(number()).run([])).toMatchObject({ 948 | ok: false, 949 | error: {message: 'expected a non-empty array, got an empty array'} 950 | }); 951 | }); 952 | }); 953 | 954 | describe('where', () => { 955 | const chars = (length: number): Decoder => 956 | string().where((s: string) => s.length === length, `expected a string of length ${length}`); 957 | 958 | const range = (min: number, max: number): Decoder => 959 | number().where( 960 | (n: number) => n >= min && n <= max, 961 | `expected a number between ${min} and ${max}` 962 | ); 963 | 964 | it('can test for strings of a given length', () => { 965 | expect(chars(7).run('7777777')).toEqual({ok: true, result: '7777777'}); 966 | 967 | expect(chars(7).run('666666')).toMatchObject({ 968 | ok: false, 969 | error: {message: 'expected a string of length 7'} 970 | }); 971 | }); 972 | 973 | it('can test for numbers in a given range', () => { 974 | expect(range(1, 9).run(7)).toEqual({ok: true, result: 7}); 975 | 976 | expect(range(1, 9).run(12)).toMatchObject({ 977 | ok: false, 978 | error: {message: 'expected a number between 1 and 9'} 979 | }); 980 | }); 981 | 982 | it('reports when the base decoder fails', () => { 983 | expect(chars(7).run(false)).toMatchObject({ 984 | ok: false, 985 | error: {message: 'expected a string, got a boolean'} 986 | }); 987 | 988 | expect(range(0, 1).run(null)).toMatchObject({ 989 | ok: false, 990 | error: {message: 'expected a number, got null'} 991 | }); 992 | }); 993 | }); 994 | 995 | describe('Result', () => { 996 | describe('can run a decoder with default value', () => { 997 | const decoder = number(); 998 | 999 | it('succeeds with the value', () => { 1000 | expect(Result.withDefault(0, decoder.run(12))).toEqual(12); 1001 | }); 1002 | 1003 | it('succeeds with the default value instead of failing', () => { 1004 | expect(Result.withDefault(0, decoder.run('999'))).toEqual(0); 1005 | }); 1006 | }); 1007 | 1008 | it('can return successes from an array of decoded values', () => { 1009 | const json: unknown = [1, true, 2, 3, 'five', 4, []]; 1010 | const jsonArray: unknown[] = Result.withDefault([], array().run(json)); 1011 | const numbers: number[] = Result.successes(jsonArray.map(number().run)); 1012 | 1013 | expect(numbers).toEqual([1, 2, 3, 4]); 1014 | }); 1015 | }); 1016 | -------------------------------------------------------------------------------- /docs/classes/_decoder_.decoder.md: -------------------------------------------------------------------------------- 1 | [@mojotech/json-type-validation](../README.md) > ["decoder"](../modules/_decoder_.md) > [Decoder](../classes/_decoder_.decoder.md) 2 | 3 | # Class: Decoder 4 | 5 | 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())`. 6 | 7 | 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. 8 | 9 | `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. 10 | 11 | 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. 12 | 13 | ## Type parameters 14 | #### A 15 | ## Hierarchy 16 | 17 | **Decoder** 18 | 19 | ## Index 20 | 21 | ### Constructors 22 | 23 | * [constructor](_decoder_.decoder.md#constructor) 24 | 25 | ### Properties 26 | 27 | * [decode](_decoder_.decoder.md#decode) 28 | 29 | ### Methods 30 | 31 | * [andThen](_decoder_.decoder.md#andthen) 32 | * [map](_decoder_.decoder.md#map) 33 | * [run](_decoder_.decoder.md#run) 34 | * [runPromise](_decoder_.decoder.md#runpromise) 35 | * [runWithException](_decoder_.decoder.md#runwithexception) 36 | * [where](_decoder_.decoder.md#where) 37 | * [anyJson](_decoder_.decoder.md#anyjson) 38 | * [array](_decoder_.decoder.md#array) 39 | * [boolean](_decoder_.decoder.md#boolean) 40 | * [constant](_decoder_.decoder.md#constant) 41 | * [dict](_decoder_.decoder.md#dict) 42 | * [fail](_decoder_.decoder.md#fail) 43 | * [intersection](_decoder_.decoder.md#intersection) 44 | * [lazy](_decoder_.decoder.md#lazy) 45 | * [number](_decoder_.decoder.md#number) 46 | * [object](_decoder_.decoder.md#object) 47 | * [oneOf](_decoder_.decoder.md#oneof) 48 | * [optional](_decoder_.decoder.md#optional) 49 | * [string](_decoder_.decoder.md#string) 50 | * [succeed](_decoder_.decoder.md#succeed) 51 | * [tuple](_decoder_.decoder.md#tuple) 52 | * [union](_decoder_.decoder.md#union) 53 | * [unknownJson](_decoder_.decoder.md#unknownjson) 54 | * [valueAt](_decoder_.decoder.md#valueat) 55 | * [withDefault](_decoder_.decoder.md#withdefault) 56 | 57 | --- 58 | 59 | ## Constructors 60 | 61 | 62 | 63 | ### `` constructor 64 | 65 | ⊕ **new Decoder**(decode: *`function`*): [Decoder](_decoder_.decoder.md) 66 | 67 | 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. 68 | 69 | 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. 70 | 71 | **Parameters:** 72 | 73 | | Param | Type | 74 | | ------ | ------ | 75 | | decode | `function` | 76 | 77 | **Returns:** [Decoder](_decoder_.decoder.md) 78 | 79 | ___ 80 | 81 | ## Properties 82 | 83 | 84 | 85 | ### `` decode 86 | 87 | **● decode**: *`function`* 88 | 89 | #### Type declaration 90 | ▸(json: *`unknown`*): `DecodeResult`<`A`> 91 | 92 | **Parameters:** 93 | 94 | | Param | Type | 95 | | ------ | ------ | 96 | | json | `unknown` | 97 | 98 | **Returns:** `DecodeResult`<`A`> 99 | 100 | ___ 101 | 102 | ## Methods 103 | 104 | 105 | 106 | ### andThen 107 | 108 | ▸ **andThen**B(f: *`function`*): [Decoder](_decoder_.decoder.md)<`B`> 109 | 110 | 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. 111 | 112 | 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. 113 | 114 | Example of adding an error message: 115 | 116 | ``` 117 | const versionDecoder = valueAt(['version'], number()); 118 | const infoDecoder3 = object({a: boolean()}); 119 | 120 | const decoder = versionDecoder.andThen(version => { 121 | switch (version) { 122 | case 3: 123 | return infoDecoder3; 124 | default: 125 | return fail(`Unable to decode info, version ${version} is not supported.`); 126 | } 127 | }); 128 | 129 | decoder.run({version: 3, a: true}) 130 | // => {ok: true, result: {a: true}} 131 | 132 | decoder.run({version: 5, x: 'abc'}) 133 | // => 134 | // { 135 | // ok: false, 136 | // error: {... message: 'Unable to decode info, version 5 is not supported.'} 137 | // } 138 | ``` 139 | 140 | Example of decoding a custom type: 141 | 142 | ``` 143 | // nominal type for arrays with a length of at least one 144 | type NonEmptyArray = T[] & { __nonEmptyArrayBrand__: void }; 145 | 146 | const nonEmptyArrayDecoder = (values: Decoder): Decoder> => 147 | array(values).andThen(arr => 148 | arr.length > 0 149 | ? succeed(createNonEmptyArray(arr)) 150 | : fail(`expected a non-empty array, got an empty array`) 151 | ); 152 | ``` 153 | 154 | **Type parameters:** 155 | 156 | #### B 157 | **Parameters:** 158 | 159 | | Param | Type | 160 | | ------ | ------ | 161 | | f | `function` | 162 | 163 | **Returns:** [Decoder](_decoder_.decoder.md)<`B`> 164 | 165 | ___ 166 | 167 | 168 | ### map 169 | 170 | ▸ **map**B(f: *`function`*): [Decoder](_decoder_.decoder.md)<`B`> 171 | 172 | 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. 173 | 174 | Example: 175 | 176 | ``` 177 | number().map(x => x * 5).run(10) 178 | // => {ok: true, result: 50} 179 | ``` 180 | 181 | **Type parameters:** 182 | 183 | #### B 184 | **Parameters:** 185 | 186 | | Param | Type | 187 | | ------ | ------ | 188 | | f | `function` | 189 | 190 | **Returns:** [Decoder](_decoder_.decoder.md)<`B`> 191 | 192 | ___ 193 | 194 | 195 | ### run 196 | 197 | ▸ **run**(json: *`unknown`*): `RunResult`<`A`> 198 | 199 | 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. 200 | 201 | Examples: 202 | 203 | ``` 204 | number().run(12) 205 | // => {ok: true, result: 12} 206 | 207 | string().run(9001) 208 | // => 209 | // { 210 | // ok: false, 211 | // error: { 212 | // kind: 'DecoderError', 213 | // input: 9001, 214 | // at: 'input', 215 | // message: 'expected a string, got 9001' 216 | // } 217 | // } 218 | ``` 219 | 220 | **Parameters:** 221 | 222 | | Param | Type | 223 | | ------ | ------ | 224 | | json | `unknown` | 225 | 226 | **Returns:** `RunResult`<`A`> 227 | 228 | ___ 229 | 230 | 231 | ### runPromise 232 | 233 | ▸ **runPromise**(json: *`unknown`*): `Promise`<`A`> 234 | 235 | Run the decoder as a `Promise`. 236 | 237 | **Parameters:** 238 | 239 | | Param | Type | 240 | | ------ | ------ | 241 | | json | `unknown` | 242 | 243 | **Returns:** `Promise`<`A`> 244 | 245 | ___ 246 | 247 | 248 | ### runWithException 249 | 250 | ▸ **runWithException**(json: *`unknown`*): `A` 251 | 252 | Run the decoder and return the value on success, or throw an exception with a formatted error string. 253 | 254 | **Parameters:** 255 | 256 | | Param | Type | 257 | | ------ | ------ | 258 | | json | `unknown` | 259 | 260 | **Returns:** `A` 261 | 262 | ___ 263 | 264 | 265 | ### where 266 | 267 | ▸ **where**(test: *`function`*, errorMessage: *`string`*): [Decoder](_decoder_.decoder.md)<`A`> 268 | 269 | 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`. 270 | 271 | ``` 272 | const chars = (length: number): Decoder => 273 | string().where( 274 | (s: string) => s.length === length, 275 | `expected a string of length ${length}` 276 | ); 277 | 278 | chars(5).run('12345') 279 | // => {ok: true, result: '12345'} 280 | 281 | chars(2).run('HELLO') 282 | // => {ok: false, error: {... message: 'expected a string of length 2'}} 283 | 284 | chars(12).run(true) 285 | // => {ok: false, error: {... message: 'expected a string, got a boolean'}} 286 | ``` 287 | 288 | **Parameters:** 289 | 290 | | Param | Type | 291 | | ------ | ------ | 292 | | test | `function` | 293 | | errorMessage | `string` | 294 | 295 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 296 | 297 | ___ 298 | 299 | 300 | ### `` anyJson 301 | 302 | ▸ **anyJson**(): [Decoder](_decoder_.decoder.md)<`any`> 303 | 304 | Escape hatch to bypass validation. Always succeeds and types the result as `any`. Useful for defining decoders incrementally, particularly for complex objects. 305 | 306 | Example: 307 | 308 | ``` 309 | interface User { 310 | name: string; 311 | complexUserData: ComplexType; 312 | } 313 | 314 | const userDecoder: Decoder = object({ 315 | name: string(), 316 | complexUserData: anyJson() 317 | }); 318 | ``` 319 | 320 | **Returns:** [Decoder](_decoder_.decoder.md)<`any`> 321 | 322 | ___ 323 | 324 | 325 | ### `` array 326 | 327 | ▸ **array**(): [Decoder](_decoder_.decoder.md)<`unknown`[]> 328 | 329 | ▸ **array**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`A`[]> 330 | 331 | 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[]`. 332 | 333 | To decode a single value that is inside of an array see `valueAt`. 334 | 335 | Examples: 336 | 337 | ``` 338 | array(number()).run([1, 2, 3]) 339 | // => {ok: true, result: [1, 2, 3]} 340 | 341 | array(array(boolean())).run([[true], [], [true, false, false]]) 342 | // => {ok: true, result: [[true], [], [true, false, false]]} 343 | 344 | const validNumbersDecoder = array() 345 | .map((arr: unknown[]) => arr.map(number().run)) 346 | .map(Result.successes) 347 | 348 | validNumbersDecoder.run([1, true, 2, 3, 'five', 4, []]) 349 | // {ok: true, result: [1, 2, 3, 4]} 350 | 351 | validNumbersDecoder.run([false, 'hi', {}]) 352 | // {ok: true, result: []} 353 | 354 | validNumbersDecoder.run(false) 355 | // {ok: false, error: {..., message: "expected an array, got a boolean"}} 356 | ``` 357 | 358 | **Returns:** [Decoder](_decoder_.decoder.md)<`unknown`[]> 359 | 360 | **Type parameters:** 361 | 362 | #### A 363 | **Parameters:** 364 | 365 | | Param | Type | 366 | | ------ | ------ | 367 | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | 368 | 369 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`[]> 370 | 371 | ___ 372 | 373 | 374 | ### `` boolean 375 | 376 | ▸ **boolean**(): [Decoder](_decoder_.decoder.md)<`boolean`> 377 | 378 | Decoder primitive that validates booleans, and fails on all other input. 379 | 380 | **Returns:** [Decoder](_decoder_.decoder.md)<`boolean`> 381 | 382 | ___ 383 | 384 | 385 | ### `` constant 386 | 387 | ▸ **constant**(value: *`true`*): [Decoder](_decoder_.decoder.md)<`true`> 388 | 389 | ▸ **constant**(value: *`false`*): [Decoder](_decoder_.decoder.md)<`false`> 390 | 391 | ▸ **constant**A(value: *`A`*): [Decoder](_decoder_.decoder.md)<`A`> 392 | 393 | Decoder primitive that only matches on exact values. 394 | 395 | 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')`. 396 | 397 | Providing the type parameter is only necessary for type-literal strings and numbers, as detailed by this table: 398 | 399 | ``` 400 | | Decoder | Type | 401 | | ---------------------------- | ---------------------| 402 | | constant(true) | Decoder | 403 | | constant(false) | Decoder | 404 | | constant(null) | Decoder | 405 | | constant('alaska') | Decoder | 406 | | constant<'alaska'>('alaska') | Decoder<'alaska'> | 407 | | constant(50) | Decoder | 408 | | constant<50>(50) | Decoder<50> | 409 | | constant([1,2,3]) | Decoder | 410 | | constant<[1,2,3]>([1,2,3]) | Decoder<[1,2,3]> | 411 | | constant({x: 't'}) | Decoder<{x: string}> | 412 | | constant<{x: 't'}>({x: 't'}) | Decoder<{x: 't'}> | 413 | ``` 414 | 415 | One place where this happens is when a type-literal is in an interface: 416 | 417 | ``` 418 | interface Bear { 419 | kind: 'bear'; 420 | isBig: boolean; 421 | } 422 | 423 | const bearDecoder1: Decoder = object({ 424 | kind: constant('bear'), 425 | isBig: boolean() 426 | }); 427 | // Type 'Decoder<{ kind: string; isBig: boolean; }>' is not assignable to 428 | // type 'Decoder'. Type 'string' is not assignable to type '"bear"'. 429 | 430 | const bearDecoder2: Decoder = object({ 431 | kind: constant<'bear'>('bear'), 432 | isBig: boolean() 433 | }); 434 | // no compiler errors 435 | ``` 436 | 437 | Another is in type-literal unions: 438 | 439 | ``` 440 | type animal = 'bird' | 'bear'; 441 | 442 | const animalDecoder1: Decoder = union( 443 | constant('bird'), 444 | constant('bear') 445 | ); 446 | // Type 'Decoder' is not assignable to type 'Decoder'. 447 | // Type 'string' is not assignable to type 'animal'. 448 | 449 | const animalDecoder2: Decoder = union( 450 | constant<'bird'>('bird'), 451 | constant<'bear'>('bear') 452 | ); 453 | // no compiler errors 454 | ``` 455 | 456 | **Parameters:** 457 | 458 | | Param | Type | 459 | | ------ | ------ | 460 | | value | `true` | 461 | 462 | **Returns:** [Decoder](_decoder_.decoder.md)<`true`> 463 | 464 | **Parameters:** 465 | 466 | | Param | Type | 467 | | ------ | ------ | 468 | | value | `false` | 469 | 470 | **Returns:** [Decoder](_decoder_.decoder.md)<`false`> 471 | 472 | **Type parameters:** 473 | 474 | #### A 475 | **Parameters:** 476 | 477 | | Param | Type | 478 | | ------ | ------ | 479 | | value | `A` | 480 | 481 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 482 | 483 | ___ 484 | 485 | 486 | ### `` dict 487 | 488 | ▸ **dict**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`Record`<`string`, `A`>> 489 | 490 | Decoder for json objects where the keys are unknown strings, but the values should all be of the same type. 491 | 492 | Example: 493 | 494 | ``` 495 | dict(number()).run({chocolate: 12, vanilla: 10, mint: 37}); 496 | // => {ok: true, result: {chocolate: 12, vanilla: 10, mint: 37}} 497 | ``` 498 | 499 | **Type parameters:** 500 | 501 | #### A 502 | **Parameters:** 503 | 504 | | Param | Type | 505 | | ------ | ------ | 506 | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | 507 | 508 | **Returns:** [Decoder](_decoder_.decoder.md)<`Record`<`string`, `A`>> 509 | 510 | ___ 511 | 512 | 513 | ### `` fail 514 | 515 | ▸ **fail**A(errorMessage: *`string`*): [Decoder](_decoder_.decoder.md)<`A`> 516 | 517 | Decoder that ignores the input json and always fails with `errorMessage`. 518 | 519 | **Type parameters:** 520 | 521 | #### A 522 | **Parameters:** 523 | 524 | | Param | Type | 525 | | ------ | ------ | 526 | | errorMessage | `string` | 527 | 528 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 529 | 530 | ___ 531 | 532 | 533 | ### `` intersection 534 | 535 | ▸ **intersection**A,B(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*): [Decoder](_decoder_.decoder.md)< `A` & `B`> 536 | 537 | ▸ **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`> 538 | 539 | ▸ **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`> 540 | 541 | ▸ **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`> 542 | 543 | ▸ **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`> 544 | 545 | ▸ **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`> 546 | 547 | ▸ **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`> 548 | 549 | Combines 2-8 object decoders into a decoder for the intersection of all the objects. 550 | 551 | Example: 552 | 553 | ``` 554 | interface Pet { 555 | name: string; 556 | maxLegs: number; 557 | } 558 | 559 | interface Cat extends Pet { 560 | evil: boolean; 561 | } 562 | 563 | const petDecoder: Decoder = object({name: string(), maxLegs: number()}); 564 | const catDecoder: Decoder = intersection(petDecoder, object({evil: boolean()})); 565 | ``` 566 | 567 | **Type parameters:** 568 | 569 | #### A 570 | #### B 571 | **Parameters:** 572 | 573 | | Param | Type | 574 | | ------ | ------ | 575 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 576 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 577 | 578 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B`> 579 | 580 | **Type parameters:** 581 | 582 | #### A 583 | #### B 584 | #### C 585 | **Parameters:** 586 | 587 | | Param | Type | 588 | | ------ | ------ | 589 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 590 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 591 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 592 | 593 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C`> 594 | 595 | **Type parameters:** 596 | 597 | #### A 598 | #### B 599 | #### C 600 | #### D 601 | **Parameters:** 602 | 603 | | Param | Type | 604 | | ------ | ------ | 605 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 606 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 607 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 608 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 609 | 610 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D`> 611 | 612 | **Type parameters:** 613 | 614 | #### A 615 | #### B 616 | #### C 617 | #### D 618 | #### E 619 | **Parameters:** 620 | 621 | | Param | Type | 622 | | ------ | ------ | 623 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 624 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 625 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 626 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 627 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 628 | 629 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E`> 630 | 631 | **Type parameters:** 632 | 633 | #### A 634 | #### B 635 | #### C 636 | #### D 637 | #### E 638 | #### F 639 | **Parameters:** 640 | 641 | | Param | Type | 642 | | ------ | ------ | 643 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 644 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 645 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 646 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 647 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 648 | | fd | [Decoder](_decoder_.decoder.md)<`F`> | 649 | 650 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F`> 651 | 652 | **Type parameters:** 653 | 654 | #### A 655 | #### B 656 | #### C 657 | #### D 658 | #### E 659 | #### F 660 | #### G 661 | **Parameters:** 662 | 663 | | Param | Type | 664 | | ------ | ------ | 665 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 666 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 667 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 668 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 669 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 670 | | fd | [Decoder](_decoder_.decoder.md)<`F`> | 671 | | gd | [Decoder](_decoder_.decoder.md)<`G`> | 672 | 673 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F` & `G`> 674 | 675 | **Type parameters:** 676 | 677 | #### A 678 | #### B 679 | #### C 680 | #### D 681 | #### E 682 | #### F 683 | #### G 684 | #### H 685 | **Parameters:** 686 | 687 | | Param | Type | 688 | | ------ | ------ | 689 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 690 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 691 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 692 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 693 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 694 | | fd | [Decoder](_decoder_.decoder.md)<`F`> | 695 | | gd | [Decoder](_decoder_.decoder.md)<`G`> | 696 | | hd | [Decoder](_decoder_.decoder.md)<`H`> | 697 | 698 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` & `B` & `C` & `D` & `E` & `F` & `G` & `H`> 699 | 700 | ___ 701 | 702 | 703 | ### `` lazy 704 | 705 | ▸ **lazy**A(mkDecoder: *`function`*): [Decoder](_decoder_.decoder.md)<`A`> 706 | 707 | 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. 708 | 709 | Example: 710 | 711 | ``` 712 | interface Comment { 713 | msg: string; 714 | replies: Comment[]; 715 | } 716 | 717 | const decoder: Decoder = object({ 718 | msg: string(), 719 | replies: lazy(() => array(decoder)) 720 | }); 721 | ``` 722 | 723 | **Type parameters:** 724 | 725 | #### A 726 | **Parameters:** 727 | 728 | | Param | Type | 729 | | ------ | ------ | 730 | | mkDecoder | `function` | 731 | 732 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 733 | 734 | ___ 735 | 736 | 737 | ### `` number 738 | 739 | ▸ **number**(): [Decoder](_decoder_.decoder.md)<`number`> 740 | 741 | Decoder primitive that validates numbers, and fails on all other input. 742 | 743 | **Returns:** [Decoder](_decoder_.decoder.md)<`number`> 744 | 745 | ___ 746 | 747 | 748 | ### `` object 749 | 750 | ▸ **object**(): [Decoder](_decoder_.decoder.md)<`Record`<`string`, `unknown`>> 751 | 752 | ▸ **object**A(decoders: *[DecoderObject](../modules/_decoder_.md#decoderobject)<`A`>*): [Decoder](_decoder_.decoder.md)<`A`> 753 | 754 | 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`. 755 | 756 | The `optional` and `constant` decoders are particularly useful for decoding objects that match typescript interfaces. 757 | 758 | To decode a single field that is inside of an object see `valueAt`. 759 | 760 | Example: 761 | 762 | ``` 763 | object({x: number(), y: number()}).run({x: 5, y: 10}) 764 | // => {ok: true, result: {x: 5, y: 10}} 765 | 766 | object().map(Object.keys).run({n: 1, i: [], c: {}, e: 'e'}) 767 | // => {ok: true, result: ['n', 'i', 'c', 'e']} 768 | ``` 769 | 770 | **Returns:** [Decoder](_decoder_.decoder.md)<`Record`<`string`, `unknown`>> 771 | 772 | **Type parameters:** 773 | 774 | #### A 775 | **Parameters:** 776 | 777 | | Param | Type | 778 | | ------ | ------ | 779 | | decoders | [DecoderObject](../modules/_decoder_.md#decoderobject)<`A`> | 780 | 781 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 782 | 783 | ___ 784 | 785 | 786 | ### `` oneOf 787 | 788 | ▸ **oneOf**A(...decoders: *[Decoder](_decoder_.decoder.md)<`A`>[]*): [Decoder](_decoder_.decoder.md)<`A`> 789 | 790 | Decoder that attempts to run each decoder in `decoders` and either succeeds with the first successful decoder, or fails after all decoders have failed. 791 | 792 | 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. 793 | 794 | Examples: 795 | 796 | ``` 797 | oneOf(string(), number().map(String)) 798 | oneOf(constant('start'), constant('stop'), succeed('unknown')) 799 | ``` 800 | 801 | **Type parameters:** 802 | 803 | #### A 804 | **Parameters:** 805 | 806 | | Param | Type | 807 | | ------ | ------ | 808 | | `Rest` decoders | [Decoder](_decoder_.decoder.md)<`A`>[] | 809 | 810 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 811 | 812 | ___ 813 | 814 | 815 | ### `` optional 816 | 817 | ▸ **optional**A(decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)< `undefined` | `A`> 818 | 819 | Decoder for values that may be `undefined`. This is primarily helpful for decoding interfaces with optional fields. 820 | 821 | Example: 822 | 823 | ``` 824 | interface User { 825 | id: number; 826 | isOwner?: boolean; 827 | } 828 | 829 | const decoder: Decoder = object({ 830 | id: number(), 831 | isOwner: optional(boolean()) 832 | }); 833 | ``` 834 | 835 | **Type parameters:** 836 | 837 | #### A 838 | **Parameters:** 839 | 840 | | Param | Type | 841 | | ------ | ------ | 842 | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | 843 | 844 | **Returns:** [Decoder](_decoder_.decoder.md)< `undefined` | `A`> 845 | 846 | ___ 847 | 848 | 849 | ### `` string 850 | 851 | ▸ **string**(): [Decoder](_decoder_.decoder.md)<`string`> 852 | 853 | Decoder primitive that validates strings, and fails on all other input. 854 | 855 | **Returns:** [Decoder](_decoder_.decoder.md)<`string`> 856 | 857 | ___ 858 | 859 | 860 | ### `` succeed 861 | 862 | ▸ **succeed**A(fixedValue: *`A`*): [Decoder](_decoder_.decoder.md)<`A`> 863 | 864 | Decoder that ignores the input json and always succeeds with `fixedValue`. 865 | 866 | **Type parameters:** 867 | 868 | #### A 869 | **Parameters:** 870 | 871 | | Param | Type | 872 | | ------ | ------ | 873 | | fixedValue | `A` | 874 | 875 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 876 | 877 | ___ 878 | 879 | 880 | ### `` tuple 881 | 882 | ▸ **tuple**A(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>]*): [Decoder](_decoder_.decoder.md)<[`A`]> 883 | 884 | ▸ **tuple**A,B(decoder: *[[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>]*): [Decoder](_decoder_.decoder.md)<[`A`, `B`]> 885 | 886 | ▸ **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`]> 887 | 888 | ▸ **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`]> 889 | 890 | ▸ **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`]> 891 | 892 | ▸ **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`]> 893 | 894 | ▸ **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`]> 895 | 896 | ▸ **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`]> 897 | 898 | Decoder for fixed-length arrays, aka Tuples. 899 | 900 | Supports up to 8-tuples. 901 | 902 | Example: 903 | 904 | ``` 905 | tuple([number(), number(), string()]).run([5, 10, 'px']) 906 | // => {ok: true, result: [5, 10, 'px']} 907 | ``` 908 | 909 | **Type parameters:** 910 | 911 | #### A 912 | **Parameters:** 913 | 914 | | Param | Type | 915 | | ------ | ------ | 916 | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>] | 917 | 918 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`]> 919 | 920 | **Type parameters:** 921 | 922 | #### A 923 | #### B 924 | **Parameters:** 925 | 926 | | Param | Type | 927 | | ------ | ------ | 928 | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>] | 929 | 930 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`]> 931 | 932 | **Type parameters:** 933 | 934 | #### A 935 | #### B 936 | #### C 937 | **Parameters:** 938 | 939 | | Param | Type | 940 | | ------ | ------ | 941 | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>] | 942 | 943 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`]> 944 | 945 | **Type parameters:** 946 | 947 | #### A 948 | #### B 949 | #### C 950 | #### D 951 | **Parameters:** 952 | 953 | | Param | Type | 954 | | ------ | ------ | 955 | | decoder | [[Decoder](_decoder_.decoder.md)<`A`>, [Decoder](_decoder_.decoder.md)<`B`>, [Decoder](_decoder_.decoder.md)<`C`>, [Decoder](_decoder_.decoder.md)<`D`>] | 956 | 957 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`]> 958 | 959 | **Type parameters:** 960 | 961 | #### A 962 | #### B 963 | #### C 964 | #### D 965 | #### E 966 | **Parameters:** 967 | 968 | | Param | Type | 969 | | ------ | ------ | 970 | | 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`>] | 971 | 972 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`]> 973 | 974 | **Type parameters:** 975 | 976 | #### A 977 | #### B 978 | #### C 979 | #### D 980 | #### E 981 | #### F 982 | **Parameters:** 983 | 984 | | Param | Type | 985 | | ------ | ------ | 986 | | 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`>] | 987 | 988 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`]> 989 | 990 | **Type parameters:** 991 | 992 | #### A 993 | #### B 994 | #### C 995 | #### D 996 | #### E 997 | #### F 998 | #### G 999 | **Parameters:** 1000 | 1001 | | Param | Type | 1002 | | ------ | ------ | 1003 | | 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`>] | 1004 | 1005 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`, `G`]> 1006 | 1007 | **Type parameters:** 1008 | 1009 | #### A 1010 | #### B 1011 | #### C 1012 | #### D 1013 | #### E 1014 | #### F 1015 | #### G 1016 | #### H 1017 | **Parameters:** 1018 | 1019 | | Param | Type | 1020 | | ------ | ------ | 1021 | | 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`>] | 1022 | 1023 | **Returns:** [Decoder](_decoder_.decoder.md)<[`A`, `B`, `C`, `D`, `E`, `F`, `G`, `H`]> 1024 | 1025 | ___ 1026 | 1027 | 1028 | ### `` union 1029 | 1030 | ▸ **union**A,B(ad: *[Decoder](_decoder_.decoder.md)<`A`>*, bd: *[Decoder](_decoder_.decoder.md)<`B`>*): [Decoder](_decoder_.decoder.md)< `A` | `B`> 1031 | 1032 | ▸ **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`> 1033 | 1034 | ▸ **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`> 1035 | 1036 | ▸ **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`> 1037 | 1038 | ▸ **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`> 1039 | 1040 | ▸ **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`> 1041 | 1042 | ▸ **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`> 1043 | 1044 | Combines 2-8 decoders of disparate types into a decoder for the union of all the types. 1045 | 1046 | 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. 1047 | 1048 | Example: 1049 | 1050 | ``` 1051 | type C = {a: string} | {b: number}; 1052 | 1053 | const unionDecoder: Decoder = union(object({a: string()}), object({b: number()})); 1054 | const oneOfDecoder: Decoder = oneOf(object({a: string()}), object({b: number()})); 1055 | ``` 1056 | 1057 | **Type parameters:** 1058 | 1059 | #### A 1060 | #### B 1061 | **Parameters:** 1062 | 1063 | | Param | Type | 1064 | | ------ | ------ | 1065 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1066 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1067 | 1068 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B`> 1069 | 1070 | **Type parameters:** 1071 | 1072 | #### A 1073 | #### B 1074 | #### C 1075 | **Parameters:** 1076 | 1077 | | Param | Type | 1078 | | ------ | ------ | 1079 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1080 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1081 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 1082 | 1083 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C`> 1084 | 1085 | **Type parameters:** 1086 | 1087 | #### A 1088 | #### B 1089 | #### C 1090 | #### D 1091 | **Parameters:** 1092 | 1093 | | Param | Type | 1094 | | ------ | ------ | 1095 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1096 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1097 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 1098 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 1099 | 1100 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D`> 1101 | 1102 | **Type parameters:** 1103 | 1104 | #### A 1105 | #### B 1106 | #### C 1107 | #### D 1108 | #### E 1109 | **Parameters:** 1110 | 1111 | | Param | Type | 1112 | | ------ | ------ | 1113 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1114 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1115 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 1116 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 1117 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 1118 | 1119 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E`> 1120 | 1121 | **Type parameters:** 1122 | 1123 | #### A 1124 | #### B 1125 | #### C 1126 | #### D 1127 | #### E 1128 | #### F 1129 | **Parameters:** 1130 | 1131 | | Param | Type | 1132 | | ------ | ------ | 1133 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1134 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1135 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 1136 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 1137 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 1138 | | fd | [Decoder](_decoder_.decoder.md)<`F`> | 1139 | 1140 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F`> 1141 | 1142 | **Type parameters:** 1143 | 1144 | #### A 1145 | #### B 1146 | #### C 1147 | #### D 1148 | #### E 1149 | #### F 1150 | #### G 1151 | **Parameters:** 1152 | 1153 | | Param | Type | 1154 | | ------ | ------ | 1155 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1156 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1157 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 1158 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 1159 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 1160 | | fd | [Decoder](_decoder_.decoder.md)<`F`> | 1161 | | gd | [Decoder](_decoder_.decoder.md)<`G`> | 1162 | 1163 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F` | `G`> 1164 | 1165 | **Type parameters:** 1166 | 1167 | #### A 1168 | #### B 1169 | #### C 1170 | #### D 1171 | #### E 1172 | #### F 1173 | #### G 1174 | #### H 1175 | **Parameters:** 1176 | 1177 | | Param | Type | 1178 | | ------ | ------ | 1179 | | ad | [Decoder](_decoder_.decoder.md)<`A`> | 1180 | | bd | [Decoder](_decoder_.decoder.md)<`B`> | 1181 | | cd | [Decoder](_decoder_.decoder.md)<`C`> | 1182 | | dd | [Decoder](_decoder_.decoder.md)<`D`> | 1183 | | ed | [Decoder](_decoder_.decoder.md)<`E`> | 1184 | | fd | [Decoder](_decoder_.decoder.md)<`F`> | 1185 | | gd | [Decoder](_decoder_.decoder.md)<`G`> | 1186 | | hd | [Decoder](_decoder_.decoder.md)<`H`> | 1187 | 1188 | **Returns:** [Decoder](_decoder_.decoder.md)< `A` | `B` | `C` | `D` | `E` | `F` | `G` | `H`> 1189 | 1190 | ___ 1191 | 1192 | 1193 | ### `` unknownJson 1194 | 1195 | ▸ **unknownJson**(): [Decoder](_decoder_.decoder.md)<`unknown`> 1196 | 1197 | Decoder identity function which always succeeds and types the result as `unknown`. 1198 | 1199 | **Returns:** [Decoder](_decoder_.decoder.md)<`unknown`> 1200 | 1201 | ___ 1202 | 1203 | 1204 | ### `` valueAt 1205 | 1206 | ▸ **valueAt**A(paths: *( `string` | `number`)[]*, decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`A`> 1207 | 1208 | 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. 1209 | 1210 | Example: 1211 | 1212 | ``` 1213 | const decoder = valueAt(['a', 'b', 0], string()); 1214 | 1215 | decoder.run({a: {b: ['surprise!']}}) 1216 | // => {ok: true, result: 'surprise!'} 1217 | 1218 | decoder.run({a: {x: 'cats'}}) 1219 | // => {ok: false, error: {... at: 'input.a.b[0]' message: 'path does not exist'}} 1220 | ``` 1221 | 1222 | 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. 1223 | 1224 | ``` 1225 | const optionalDecoder = valueAt(['a', 'b', 'c'], optional(string())); 1226 | 1227 | optionalDecoder.run({a: {b: {c: 'surprise!'}}}) 1228 | // => {ok: true, result: 'surprise!'} 1229 | 1230 | optionalDecoder.run({a: {b: 'cats'}}) 1231 | // => {ok: false, error: {... at: 'input.a.b.c' message: 'expected an object, got "cats"'} 1232 | 1233 | optionalDecoder.run({a: {b: {z: 1}}}) 1234 | // => {ok: true, result: undefined} 1235 | ``` 1236 | 1237 | **Type parameters:** 1238 | 1239 | #### A 1240 | **Parameters:** 1241 | 1242 | | Param | Type | 1243 | | ------ | ------ | 1244 | | paths | ( `string` | `number`)[] | 1245 | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | 1246 | 1247 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 1248 | 1249 | ___ 1250 | 1251 | 1252 | ### `` withDefault 1253 | 1254 | ▸ **withDefault**A(defaultValue: *`A`*, decoder: *[Decoder](_decoder_.decoder.md)<`A`>*): [Decoder](_decoder_.decoder.md)<`A`> 1255 | 1256 | Decoder that always succeeds with either the decoded value, or a fallback default value. 1257 | 1258 | **Type parameters:** 1259 | 1260 | #### A 1261 | **Parameters:** 1262 | 1263 | | Param | Type | 1264 | | ------ | ------ | 1265 | | defaultValue | `A` | 1266 | | decoder | [Decoder](_decoder_.decoder.md)<`A`> | 1267 | 1268 | **Returns:** [Decoder](_decoder_.decoder.md)<`A`> 1269 | 1270 | ___ 1271 | 1272 | --------------------------------------------------------------------------------