{"id":16594570,"url":"https://github.com/owanturist/decode-json","last_synced_at":"2026-05-18T09:33:26.610Z","repository":{"id":55101082,"uuid":"290426149","full_name":"owanturist/decode-json","owner":"owanturist","description":"Convert unknown JSON values to TypeScript safe structures","archived":false,"fork":false,"pushed_at":"2021-01-15T13:19:36.000Z","size":168,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-09T04:32:38.260Z","etag":null,"topics":["decoding","json","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/owanturist.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-26T07:24:16.000Z","updated_at":"2022-09-29T12:10:49.000Z","dependencies_parsed_at":"2022-08-14T12:00:45.311Z","dependency_job_id":null,"html_url":"https://github.com/owanturist/decode-json","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/owanturist/decode-json","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owanturist%2Fdecode-json","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owanturist%2Fdecode-json/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owanturist%2Fdecode-json/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owanturist%2Fdecode-json/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/owanturist","download_url":"https://codeload.github.com/owanturist/decode-json/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owanturist%2Fdecode-json/sbom","scorecard":{"id":715604,"data":{"date":"2025-08-11","repo":{"name":"github.com/owanturist/decode-json","commit":"b6ef89eed474b2e46285922c97919e5bd8c102b7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/12 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"32 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T09:32:53.351Z","repository_id":55101082,"created_at":"2025-08-22T09:32:53.352Z","updated_at":"2025-08-22T09:32:53.352Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33172601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"ssl_error","status_checked_at":"2026-05-18T09:27:28.300Z","response_time":71,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decoding","json","typescript"],"created_at":"2024-10-11T23:46:40.886Z","updated_at":"2026-05-18T09:33:26.589Z","avatar_url":"https://github.com/owanturist.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# decode-json\n\n![Coverage Status](https://img.shields.io/coveralls/github/owanturist/decode-json/master.svg)\n![Minified + gzip](https://flat.badgen.net/bundlephobia/minzip/decode-json)\n![Dependency count](https://flat.badgen.net/bundlephobia/dependency-count/decode-json)\n![Known Vulnerabilities](https://snyk.io/test/github/owanturist/decode-json/badge.svg?style=flat-square)\n![Types](https://flat.badgen.net/npm/types/decode-json)\n![Total downloads](https://flat.badgen.net/npm/dt/decode-json)\n\n\u003e The package you are about to use has been done under inspiration of [Elm lang](https://elm-lang.org/) and [elm/json](https://package.elm-lang.org/packages/elm/json/latest/) package particularly.\n\nUsing TypeScript is a great way to prevent some bugs during compile time but nothing can save us from runtime exceptions. Today \"height\" field coming from the `GET /tallest-building` endpoint is a `number` and you call `.toFixed(2)` to format it but next day it becomes a preformatted `string` and the app crashes with `toFixed is not a function`. The same thing could happen when an application uses `localStorage` and somebody changes a format to keep credentials token or last opened product information - value exists so you assume that it is valid but runtime error will make you unhappy very soon. Sounds familiar, doesn't?\n\nAs a little attempt to workaround the issue we can try to protect our programs from unexpected data to come. To do so we should be able to explain what data we expect and how it should be transformed so an application can use it.\n\n## Installation\n\n```bash\n# with npm\nnpm install decode-json --save\n\n# with yarn\nyarn install decode-json\n```\n\n```ts\n// with skypack\nimport Decode, { Decoder } from 'https://cdn.skypack.dev/decode-json'\nimport errorToHumanReadable from 'https://cdn.skypack.dev/decode-json/error-to-human-readable'\n\n// minifield version\nimport Decode, { Decoder } from 'https://cdn.skypack.dev/decode-json?min'\nimport errorToHumanReadable from 'https://cdn.skypack.dev/decode-json/error-to-human-readable?min'\n```\n\n## Example\n\nLet assume you are building a Star Wars fan wep application and you'd like to request Luke Skywalker's data from [swapi.dev/api/people/1](https://swapi.dev/api/people/1). You will get something like that:\n\n```json\n{\n  \"name\": \"Luke Skywalker\",\n  \"birth_year\": \"19BBY\",\n  \"height\": \"172\",\n  \"mass\": \"77\"\n}\n```\n\nThis is how it can be decoded safely:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst parseFloatDecoder: Decoder\u003cnumber\u003e = Decode.oneOf([\n  Decode.float, // in case the value is float already\n  Decode.string.chain(str =\u003e {\n    const num = Number(str || '_') // prevents Number('') === 0\n\n    if (isNaN(num)) {\n      return Decode.fail(`Could not parse \"${str}\" as a float`)\n    }\n\n    return Decode.succeed(num)\n  })\n])\n\nconst characterDecoder = Decode.shape({\n  name: Decode.field('name').string,\n  birthYear: Decode.field('birth_year').string,\n  height: Decode.field('height').of(parseFloatDecoder),\n  mass: Decode.field('mass').of(parseFloatDecoder)\n})\n\nconst response = await fetch('https://swapi.dev/api/people/1')\nconst data = await response.json()\nconst characterResult = characterDecoder.decode(data)\n```\n\nThe decoder above does next steps:\n\n1. tries to extract a value from `name` field of the `response` and checks the value is a string\n1. tries to extract a value from `birth_year` field of the `response` and checks the value is a string\n1. tries to extract a value from `height` field of the `response` and parses the value as a float\n1. tries to extract a value from `mass` field of the `response` and parses the value as a float\n1. creates an output object with field `name`, `birthYear`, `height` and `mass` with values assigned respectively.\n\nIf a response reflects our expectations so the results for `swapi.dev/api/people/1` will look like:\n\n```ts\ncharacterDecoder.decode(data)\n// == {\n//   value: {\n//     name: 'Luke Skywalker',\n//     birthYear: '19BBY',\n//     height: 172,\n//     mass: 77\n//   }\n// }\n```\n\nBut as soon as one of the 1-4 steps fails you will get a detailed report why it happened. Let's say the server sends birth height as a formatted string with a unit for some reason. Here is what you'll get when `\"172\"` string becomes `\"172 cm\"`:\n\n```ts\ncharacterDecoder.decode(data)\n// == {\n//   error: {\n//     type: 'IN_FIELD',\n//     field: 'height',\n//     error: {\n//       type: 'FAILURE',\n//       message: 'Could not parse \"172 cm\" as a float',\n//       source: '172 cm'\n//     }\n//   }\n// }\n```\n\nAnd the trick is that by using a decoder a developer assumes that decode result might be either succeed or failed but not blindly trust that with `200` status code you'll get a valid data. So there is no way for the developer to ignore the awareness of failure but only handle the case somehow. Is not it an amazing concept?\n\n## API\n\n- [`DecodeResult`](#decoderesult)\n- [`Decoder`](#decoder)\n  - [`Decoder.map`](#decodermap)\n  - [`Decoder.chain`](#decoderchain)\n  - [`Decoder.decode`](#decoderdecode)\n  - [`Decoder.decodeJson`](#decoderdecodejson)\n- `Decode`\n  - [`Decode.string`](#decodestring)\n  - [`Decode.boolean`](#decodeboolean)\n  - [`Decode.int`](#decodeint)\n  - [`Decode.float`](#decodefloat)\n  - [`Decode.unknown`](#decodeunknown)\n  - [`Decode.exact`](#decodeexact)\n  - [`Decode.record`](#decoderecord)\n  - [`Decode.list`](#decodelist)\n  - [`Decode.keyValue`](#decodekeyvalue)\n  - [`Decode.record`](#decoderecord)\n  - [`Decode.tuple`](#decodetuple)\n  - [`Decode.oneOf`](#decodeoneof)\n  - [`Decode.lazy`](#decodelazy)\n  - [`Decode.fail`](#decodefail)\n  - [`Decode.succeed`](#decodesucceed)\n  - [`Decode.field`](#decodefield)\n  - [`Decode.index`](#decodeindex)\n  - [`Decode.optional`](#decodeoptional)\n- `DecodeOptional`\n  - [`DecodeOptional.string`](#decodeoptionalstring)\n  - [`DecodeOptional.boolean`](#decodeoptionalboolean)\n  - [`DecodeOptional.int`](#decodeoptionalint)\n  - [`DecodeOptional.float`](#decodeoptionalfloat)\n  - [`DecodeOptional.list`](#decodeoptionallist)\n  - [`DecodeOptional.record`](#decodeoptionalrecord)\n  - [`DecodeOptional.keyValue`](#decodeoptionalkeyvalue)\n  - [`DecodeOptional.field`](#decodeoptionalfield)\n  - [`DecodeOptional.index`](#decodeoptionalindex)\n- [`RequiredDecodePath`](#requireddecodepath)\n- [`OptionalDecodePath`](#optionaldecodepath)\n- [`DecodeError`](#decodeerror)\n  - [`EXPECT_STRING`](#expect_string)\n  - [`EXPECT_BOOLEAN`](#expect_boolean)\n  - [`EXPECT_FLOAT`](#expect_float)\n  - [`EXPECT_INT`](#expect_int)\n  - [`EXPECT_EXACT`](#expect_exact)\n  - [`EXPECT_ARRAY`](#expect_array)\n  - [`EXPECT_OBJECT`](#expect_object)\n  - [`FAILURE`](#failure)\n  - [`REQUIRED_INDEX`](#required_index)\n  - [`REQUIRED_FIELD`](#required_field)\n  - [`AT_INDEX`](#at_index)\n  - [`IN_FIELD`](#in_field)\n  - [`OPTIONAL`](#optional)\n  - [`ONE_OF`](#one_of)\n  - [`RUNTIME_EXCEPTION`](#runtime_exception)\n- [`DecodeJsonError`](#decodejsonerror)\n  - [`INVALID_JSON`](#invalid_json)\n\n### `DecodeResult`\n\nThis is what you always get as result of running both [`Decoder.decode`](#decoderdecode) and [`Decoder.decodeJson`](#decoderdecodejson) methods. It can be either `{ value: T }` when a decoding has been successfully passed or `{ error: E }` when any of decode, json parse or runtime error occur.\n\n```ts\nimport Decode, { Decoder, DecodeResult, DecodeError } from 'decode-json'\n\nconst ageDecoder: Decoder\u003cnumber\u003e = Decode.field('age').int\nconst badResult = ageDecoder.decode('oops')\n// == { error: { type: 'EXPECT_OBJECT', source: 'oops' } }\nconst goodResult = ageDecoder.decode({ age: 27 }) // == { value: 27 }\n```\n\nIt's recommended to avoid destructuring assignment for `DecodeResult` because TypeScript won't help you with error handling:\n\n```ts\nconst { error, value } = ageDecoder.decode({ age: 27 })\n\nif (error) {\n  showError(error)\n} else {\n  showAge(value) // TS complains with \"Object is possibly 'undefined'\"\n}\n```\n\nTo workaround the issue just don't use destructuring assignment:\n\n```ts\nconst ageResult = ageDecoder.decode({ age: 27 })\n\nif (ageResult.error) {\n  showError(ageResult.error)\n} else {\n  showAge(ageResult.value) // no complains here\n}\n```\n\n### `Decoder`\n\nThis is a declarative block of decode system which knows how to transform an expected data to a final result.\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\ninterface User {\n  id: string\n  nickname: string\n  active: boolean\n  age: number\n}\n\nconst userDecoder: Decoder\u003cUser\u003e = Decode.shape({\n  id: Decode.field('uuid').string,\n  nickname: Decode.field('username').string,\n  active: Decode.field('is_active').boolean,\n  age: Decode.field('age').int\n})\n```\n\n#### `Decoder.map`\n\nTransforms decoded value. It can both keep the same type or change to another one:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst messageDecoder: Decoder\u003cstring\u003e = Decode.string.map(message =\u003e\n  message.trim()\n)\nconst charCountDecoder: Decoder\u003cnumber\u003e = Decode.string.map(\n  message =\u003e message.trim().length\n)\n```\n\n#### `Decoder.chain`\n\nTransforms decoded value decoder:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst displayDateDecoder: Decoder\u003cstring\u003e = Decode.field(\n  'active'\n).boolean.chain(active =\u003e {\n  return active\n    ? Decode.field('last_activity').string\n    : Decode.field('start_date').string\n})\n\ndisplayDateDecoder.decode({\n  active: false,\n  last_activity: '30 sec ago',\n  start_date: '1 Sep 2020'\n}).value // == '1 Sep 2020'\n\ndisplayDateDecoder.decode({\n  active: true,\n  last_activity: '30 sec ago',\n  start_date: '1 Sep 2020'\n}).value // == '30 sec ago'\n```\n\n#### `Decoder.decode`\n\nRuns a decoder on unknown input data:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.boolean.decode('I am unknown input').error\n// == { type: 'EXPECT_BOOLEAN', source: 'I am unknown input' }\n\nDecode.boolean.decode(false).value // == false\n```\n\n#### `Decoder.decodeJson`\n\nRuns a decoder on JSON string. Does the same as [`Decoder.decode`](#decoderdecode) but parses JSON first:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.string.decodeJson('I am just a string').error\n// == {\n//   type: 'INVALID_JSON',\n//   error: new SyntaxError('Unexpected token I in JSON at position 0'),\n//   source: 'I am just a string'\n// }\n\nconst goodJson = Decode.string.decodeJson('\"I am a JSON string\"').value\n// == 'I am a JSON string'\n```\n\n### `Decode.string`\n\nDecodes a string value:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.string.decode(null).error // == { type: 'EXPECT_STRING', source: null }\nDecode.string.decode(true).error // == { type: 'EXPECT_STRING', source: true }\nDecode.string.decode(1234).error // == { type: 'EXPECT_STRING', source: 1234 }\nDecode.string.decode(12.3).error // == { type: 'EXPECT_STRING', source: 12.3 }\nDecode.string.decode('hi').value // == 'hi'\n```\n\n### `Decode.boolean`\n\nDecodes a boolean value:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.boolean.decode(null).error // == { type: 'EXPECT_BOOLEAN', source: null }\nDecode.boolean.decode(1234).error // == { type: 'EXPECT_BOOLEAN', source: 1234 }\nDecode.boolean.decode(12.3).error // == { type: 'EXPECT_BOOLEAN', source: 12.3 }\nDecode.boolean.decode('hi').error // == { type: 'EXPECT_BOOLEAN', source: 'hi' }\nDecode.boolean.decode(true).value // true\n```\n\n### `Decode.int`\n\nDecodes an integer value:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.int.decode(null).error // == { type: 'EXPECT_INT', source: null }\nDecode.int.decode(true).error // == { type: 'EXPECT_INT', source: true }\nDecode.int.decode('hi').error // == { type: 'EXPECT_INT', source: 'hi' }\nDecode.int.decode(12.3).error // == { type: 'EXPECT_INT', source: 12.3 }\nDecode.int.decode(1234).value // 1234\n```\n\n### `Decode.float`\n\nDecodes a float value:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.float.decode(null).error // == { type: 'EXPECT_FLOAT', source: null }\nDecode.float.decode(true).error // == { type: 'EXPECT_FLOAT', source: true }\nDecode.float.decode('hi').error // == { type: 'EXPECT_FLOAT', source: 'hi' }\nDecode.float.decode(12.3).value // 12.3\nDecode.float.decode(1234).value // 1234\n```\n\n### `Decode.unknown`\n\nDoes not do anything with an incoming value, just bring it into TS as a unknown. This can be useful if you have particularly complex data that you would like to deal with later. Or if you are going to send it out a http request and do not care about its structure. Decoding of unknown never fails.\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.unknown.decode(window.location).value // == window.location\n```\n\n### `Decode.exact`\n\nDecodes an exact primitive (either `string`, `number`, `boolean` or `null`) values.\n\n```ts\nimport Decode from 'decode-json'\n\nconst pointDecoder = Decode.shape({\n  type: Decode.field('type').exact('POINT'),\n  x: Decode.field('axis_x').float,\n  y: Decode.field('axis_y').float\n})\n\npointDecoder.decode({\n  type: 'LINE',\n  x0: 1.2,\n  y0: 3.4,\n  x1: 5.6,\n  x1: 7.8\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'type',\n//   error: {\n//     type: 'EXPECT_EXACT',\n//     value: 'POINT',\n//     source: 'LINE'\n//   }\n// }\n```\n\nMight be used with [`Decode.oneOf`](#decodeoneof) to build enum decoders:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nenum Role {\n  User,\n  Manager,\n  Admin\n}\n\nconst roleDecoder: Decoder\u003cRole\u003e = Decode.oneOf([\n  Decode.exact('USER', Role.User),\n  Decode.exact('MANAGER', Role.Manager),\n  Decode.exact('ADMIN', Role.Admin)\n])\n```\n\n### `Decode.record`\n\nDecodes a key-value pairs as object:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst activeUsers: Decoder\u003cRecord\u003cstring, boolean\u003e\u003e = Decode.record(\n  Decode.boolean\n)\n\nactiveUsers.decode({\n  John: false,\n  Martin: false,\n  Scott: true\n}).value // == { John: false, Martin: false, Scott: true }\n\nactiveUsers.decode({\n  John: false,\n  Martin: false,\n  Scott: 'yes'\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'Scott',\n//   error: {\n//     type: 'EXPECT_BOOLEAN',\n//     source: 'yes'\n//   }\n// }\n```\n\n### `Decode.list`\n\nDecodes a list of values:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\ninterface User {\n  id: number\n  username: string\n}\n\nconst userDecoder: Decoder\u003cUser\u003e = Decode.shape({\n  id: Decode.field('id').int,\n  username: Decode.field('user_name').string\n})\n\nDecode.list(userDecoder).decode([\n  {\n    id: 0,\n    user_name: 'superstar'\n  },\n  {\n    id: 1,\n    user_name: 'boss'\n  }\n]).value // == [{ id: 0, username: 'superstar' }, { id: 1, username: 'boss' }]\n\nDecode.list(userDecoder).decode([\n  {\n    id: 0,\n    user_name: 'lollypop'\n  },\n  {\n    id: 1,\n    name: 'boss'\n  }\n]).error\n// == {\n//   type: 'AT_INDEX',\n//   position: 1,\n//   error: {\n//     type: 'REQUIRED_FIELD',\n//     name: 'user_name',\n//     source: {\n//       id: 1,\n//       name: 'boss'\n//     }\n//   }\n// }\n```\n\n### `Decode.keyValue`\n\nDecodes key-value pairs as list of tuples:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst activeUsers: Decoder\u003cArray\u003c[string, boolean]\u003e\u003e = Decode.keyValue(\n  Decode.boolean\n)\n\nactiveUsers.decode({\n  John: false,\n  Martin: false,\n  Scott: true\n}).value // == [[ 'John', false ], [ 'Martin', false ], [ 'Scott', true ]]\n\nactiveUsers.decode({\n  John: false,\n  Martin: false,\n  Scott: 'yes'\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'Scott',\n//   error: {\n//     type: 'EXPECT_BOOLEAN',\n//     source: 'yes'\n//   }\n// }\n```\n\nYou also safely convert a key value from string to something else:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nenum Currency {\n  Rub,\n  Eur,\n  Usd\n}\n\nconst currencyFromCode = (code: string): Currency =\u003e {\n  switch (code) {\n    case 'rub':\n      return { value: Currency.Rub }\n    case 'eur':\n      return { value: Currency.Eur }\n    case 'usd':\n      return { value: Currency.Usd }\n    default: {\n      error: `Unknown currency code \"${code}\"`\n    }\n  }\n}\n\nconst balance: Decoder\u003cArray\u003c[Currency, number]\u003e\u003e = Decode.keyValue(\n  currencyFromCode,\n  Decode.float\n)\n\nactiveUsers.decode({\n  rub: 42000.1,\n  eur: 2400.87,\n  usd: 13000.51\n}).value\n// == [\n//   [ Currency.Rub', 42000.1 ],\n//   [ Currency.Eur, 2400.87 ],\n//   [ Currency.Usd, 13000.51 ]\n// ]\n\nactiveUsers.decode({\n  rub: 42000.1,\n  eur: 2400.87,\n  usd: 13000.51,\n  cny: 7912.08\n}).error\n// == {\n//   type: 'FAILURE',\n//   message: 'Unknown currency code \"cny\"'\n//   source: 'cny'\n// }\n```\n\n### `Decode.record`\n\nCombines decoded values to the corresponding object's fields:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\ninterface User {\n  id: string\n  nickname: string\n  active: boolean\n  age: number\n}\n\nconst userDecoder: Decoder\u003cUser\u003e = Decode.shape({\n  id: Decode.field('uuid').string,\n  nickname: Decode.field('username').string,\n  active: Decode.field('is_active').boolean,\n  age: Decode.field('age').int\n})\n\nuserDecoder.decode({\n  uuid: 'user_12319238',\n  username: 'wolverine',\n  is_active: false,\n  age: 61\n}).value\n// == {\n//   id: 'user_12319238',\n//   nickname: 'wolverine',\n//   active: false,\n//   age: 61\n// }\n\nuserDecoder.decode({\n  uuid: 'user_12319238',\n  username: 'wolverine',\n  is_active: 'yes',\n  age: 61\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   field: 'is_active',\n//   error: {\n//     type: 'EXPECT_BOOLEAN',\n//     source: 'yes'\n//   }\n// }\n```\n\n\u003e _Note_: `Decode.record` **does not** decode any value! It only combines another decoders' values.\n\n\u003e You also notice that shape's fields does describe any path fragments for assigned decoders - these are only destinations of decoded values.\n\n### `Decode.tuple`\n\nCombines decoded values to the corresponding tuple segments:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst pointDecoder: Decoder\u003c[number, number]\u003e = Decode.tuple(\n  Decode.field('x').float,\n  Decode.field('y').float\n)\n\npointDecoder.decode({\n  x: 12.34,\n  y: 56.78\n}).value // == [ 12.34, 56.78 ]\n\npointDecoder.decode({\n  x: 12.34,\n  y: '56.78'\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   field: 'y',\n//   error: {\n//     type: 'EXPECT_FLOAT',\n//     source: '56.78'\n//   }\n// }\n```\n\n\u003e _Note_: `Decode.tuple` **does not** decode any value! It only combines another decoders' values.\n\n\u003e You also notice that tuple's segments does describe any path fragments for assigned decoders - these are only destinations of decoded values.\n\n### `Decode.oneOf`\n\nTry a bunch of different decoders. This can be useful if the values may come in a couple different formats.\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst dateDecoder: Decoder\u003cDate\u003e = Decode.oneOf([\n  Decode.int.map(timestamp =\u003e new Date(timestamp)),\n  Decode.string.chain(datetime =\u003e {\n    const date = new Date(datetime)\n\n    if (isNaN(date.getMilliseconds())) {\n      return Decode.fail(`Could not create a date from \"${datetime}\"`)\n    }\n\n    return Decode.succeed(date)\n  })\n])\n\ndateDecoder.decode(1609542413856).value // == new Date('Fri, 01 Jan 2021 23:06:53 GMT')\ndateDecoder.decode('Fri, 01 Jan 2021 23:06:53 GMT').value // == new Date(1609542413856)\ndateDecoder.decode('01|01|2021').error\n// == {\n//   type: 'ONE_OF',\n//   errors: [\n//     {\n//       type: 'EXPECT_INT',\n//       source: '01|01|2021'\n//     },\n//     {\n//       type: 'FAILURE',\n//       message: 'Could not create a date from \"01|01|2021\"',\n//       source: '01|01|2021'\n//     }\n//   ]\n// }\n```\n\nThis is a powerful tool to work with inconsistent data:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\ninterface User {\n  id: string\n  nickname: string\n  active: boolean\n  age: number\n}\n\nconst userDecoder: Decoder\u003cUser\u003e = Decode.oneOf([\n  // legacy version\n  Decode.shape({\n    id: Decode.field('index').int.map(String),\n    nickname: Decode.field('name').string,\n    active: Decode.field('is_active').boolean,\n    age: Decode.field('years').int\n  }),\n\n  // latest version\n  Decode.shape({\n    id: Decode.field('uuid').string,\n    nickname: Decode.field('username').string,\n    active: Decode.field('isActive').boolean,\n    age: Decode.field('age').int\n  })\n])\n\nuserDecoder.decode({\n  index: 0,\n  name: 'Rachel',\n  is_active: true,\n  years: 30\n}).value\n// == {\n//   id: '0',\n//   nickname: 'Rachel',\n//   active: true,\n//   age: 30\n// }\n\nuserDecoder.decode({\n  uuid: 'uuid-id-is-here',\n  username: 'Ross',\n  isActive: true,\n  age: 32\n}).value\n// == {\n//   id: 'uuid-id-is-here',\n//   nickname: 'Ross',\n//   active: true,\n//   age: 32\n// }\n```\n\nIt also can be used to set a default value if all of the decoders fails for whatever reason:\n\n```ts\nimport Decode from 'decode-json'\n\nconst configDecoder = Decode.oneOf([\n  Decode.shape({\n    hostUrl: Decode.field('HOST_URL').string,\n    apiVersion: Decode.field('API_VERSION').int\n  }),\n\n  Decode.succeed({\n    hostUrl: 'localhost:8000',\n    apiVersion: 1\n  })\n])\n\nconfigDecoder.decode(null).value\n// == {\n//   hostUrl: 'localhost:8000',\n//   apiVersion: 1\n// }\n```\n\n### `Decode.lazy`\n\nSometimes you have a recursive data structures, like comments with responses, which also are comments with responses, which also... you got the point. To make sure a decoder unrolls lazily it should use `Decode.lazy` wrapper:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\ninterface Comment {\n  message: string\n  responses: Array\u003cComment\u003e\n}\n\nconst commentDecoder: Decoder\u003cComment\u003e = Decode.shape({\n  message: Decode.field('mes').string,\n  responses: Decode.field('res').list(Decode.lazy(() =\u003e commentDecoder))\n})\n\ncommentDecoder.decode({\n  mes: 'oops',\n  res: [\n    {\n      mes: 'yes',\n      res: [\n        {\n          mes: 'here we go again',\n          res: []\n        }\n      ]\n    },\n    {\n      mes: 'no',\n      res: [\n        {\n          mes: 'that is right',\n          res: [\n            {\n              mes: 'agree',\n              res: []\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}).value\n// == {\n//   message: 'oops',\n//   responses: [\n//     {\n//       message: 'yes',\n//       responses: [\n//         {\n//           message: 'here we go again',\n//           responses: []\n//         }\n//       ]\n//     },\n//     {\n//       message: 'no',\n//       responses: [\n//         {\n//           message: 'that is right',\n//           responses: [\n//             {\n//               message: 'agree',\n//               responses: []\n//             }\n//           ]\n//         }\n//       ]\n//     }\n//   ]\n// }\n```\n\n### `Decode.fail`\n\nIgnores a decoding value and make the decoder fail. This is handy when used with [`Decode.oneOf`](#decodeoneof) or [`Decoder.chain`](#decoderchain) where you want to give a custom error message in some cases.\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst positiveIntDecoder: Decoder\u003cnumber\u003e = Decode.int.chain(int =\u003e {\n  if (int \u003e 0) {\n    return Decode.succeed(int)\n  }\n\n  return Decode.fail(`Expects positive int but get ${int} instead`)\n})\n\npositiveIntDecoder.decode(42).value // == 42\n\npositiveIntDecoder.decode(-1).error\n// == {\n//   type: 'FAILURE',\n//   message: 'Expects positive int but get -1 instead',\n//   source: -1\n// }\n```\n\n\u003e _Note_: see [`Decode.oneOf`](#decodeoneof) and [`Decoder.chain`](#decoderchain) for more examples.\n\n### `Decode.succeed`\n\nIgnores a decoding value and produce a certain value. Handy when used with [`Decode.oneOf`](#decodeoneof) or [`Decoder.chain`](#decoderchain).\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst messageDecoder: Decoder\u003cstring\u003e = Decode.string.chain(message =\u003e {\n  if (message.length \u003e= 10) {\n    return Decode.succeed(message)\n  }\n\n  return Decode.fail(\n    `An input message is only ${message.length} chars long but at least 10 is required`\n  )\n})\n\nmessageDecoder.decode('Quite long message').value // == 'Quite long message'\nmessageDecoder.decode('Short').error\n// == An input message is only 5 chars long but at least 10 is required\nmessageDecoder.decode(123).error // == { type: 'EXPECT_STRING', source: 123 }\n```\n\nCan be used to define hardcoded values.\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst pointDecoder = Decoder.shape({\n  x: Decode.index(0).float,\n  y: Decode.index(1).float,\n  z: Decode.succeed(0)\n})\n\npointDecoder.decode([0.31, 8.17]).value // == { x: 0.31, y: 8.17, z: 0 }\n```\n\n\u003e _Note_: see [`Decode.oneOf`](#decodeoneof) and [`Decoder.chain`](#decoderchain) for more examples.\n\n### `Decode.field`\n\nCreates a [`RequiredDecodePath`](#requireddecodepath) instance.\n\n```ts\nimport Decode, { RequiredDecodePath } from 'decode-json'\n\nconst currentUserPath: RequiredDecodePath = Decode.field('current_user')\n```\n\n### `Decode.index`\n\nCreates a [`RequiredDecodePath`](#requireddecodepath) instance.\n\n```ts\nimport Decode, { RequiredDecodePath } from 'decode-json'\n\nconst secondPointPath: RequiredDecodePath = Decode.index(1)\n```\n\n### `Decode.optional`\n\nCreates `DecodeOptional` instance.\n\n### `DecodeOptional.string`\n\nBehaves exactly as [`Decode.string`](#decodestring) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.string.decode(1234).error\n// == {\n//   type: 'OPTIONAL',\n//   error: { type: 'EXPECT_STRING', source: 1234 }\n// }\nDecode.optional.string.decode(null).value // == null\nDecode.optional.string.decode(undefined).value // == null\nDecode.optional.string.decode('hi').value // == 'hi'\n```\n\n### `DecodeOptional.boolean`\n\nBehaves exactly as [`Decode.boolean`](#decodeboolean) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.boolean.decode(1234).error\n// == {\n//   type: 'OPTIONAL',\n//   error: { type: 'EXPECT_BOOLEAN', source: 1234 }\n// }\nDecode.optional.boolean.decode(null).value // == null\nDecode.optional.boolean.decode(undefined).value // == null\nDecode.optional.boolean.decode(true).value // == true\n```\n\n### `DecodeOptional.int`\n\nBehaves exactly as [`Decode.int`](#decodeint) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.int.decode(12.3).error\n// == {\n//   type: 'OPTIONAL',\n//   error: { type: 'EXPECT_INT', source: 12.3 }\n// }\nDecode.optional.int.decode(null).value // == null\nDecode.optional.int.decode(undefined).value // == null\nDecode.optional.int.decode(1234).value // == 1234\n```\n\n### `DecodeOptional.float`\n\nBehaves exactly as [`Decode.float`](#decodefloat) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.float.decode(false).error\n// == {\n//   type: 'OPTIONAL',\n//   error: { type: 'EXPECT_FLOAT', source: false }\n// }\nDecode.optional.float.decode(null).value // == null\nDecode.optional.float.decode(undefined).value // == null\nDecode.optional.float.decode(12.3).value // == 12.3\n```\n\n### `DecodeOptional.list`\n\nBehaves exactly as [`Decode.list`](#decodelist) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst idsDecoder: Decoder\u003cnull | Array\u003cnumber\u003e\u003e = Decode.optional.list(\n  Decode.int\n)\n\nidsDecoder.decode(null).value // == null\nidsDecoder.decode(undefined).value // == null\nidsDecoder.decode([0, 2, 3]).value // == [ 0, 2, 3 ]\n```\n\nNote that `optional` statement here is assigned to `list` decoder, but not to it's items, so this one will fail:\n\n```ts\nidsDecoder.decode([ 0, null, 2, 3 ]).error\n{\n  type: 'OPTIONAL',\n  error: {\n    type: 'AT_INDEX',\n    position: 1,\n    error: { type: 'EXPECT_INT', source: null }\n  }\n}\n```\n\nIf you expect both array and the items to be optional you can do it like that:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst idsDecoder: Decoder\u003cnull | Array\u003cnull | number\u003e\u003e = Decode.optional.list(\n  Decode.optional.int\n)\n\nidsDecoder.decode(null).value // === null\nidsDecoder.decode([0, null, 2, 3]).value // === [ 0, null, 2, 3 ]\n```\n\n### `DecodeOptional.record`\n\nBehaves exactly as [`Decode.record`](#decoderecord) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst blackListDecoder: Decoder\u003cnull | Record\u003c\n  string,\n  boolean\n\u003e\u003e = Decode.optional.record(Decode.boolean)\n\nblackListDecoder.decode(null).value // == null\nblackListDecoder.decode(undefined).value // == null\nblackListDecoder.decode({\n  John: false,\n  Emma: true,\n  Tom: true\n}).value\n// == {\n//   John: false,\n//   Emma: true,\n//   Tom: true\n// }\n```\n\nNote that `optional` statement here is assigned to `record` decoder, but not it's items, so this one will fail:\n\n```ts\nblackListDecoder.decode({\n  John: false,\n  Emma: true,\n  Tom: true,\n  Adam: null\n}).error\n// == {\n//   type: 'OPTIONAL',\n//   error: {\n//     type: 'IN_FIELD',\n//     name: 'Adam',\n//     error: { type: 'EXPECT_BOOLEAN', source: null }\n//   }\n// }\n```\n\nIf you expect both object and the items to be optional you can do it like that:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst blackListDecoder: Decoder\u003cnull | Record\u003c\n  string,\n  null | boolean\n\u003e\u003e = Decode.optional.record(Decode.boolean)\n\nblackListDecoder.decode(null).value // === null\nblackListDecoder.decode({\n  John: false,\n  Emma: true,\n  Tom: true,\n  Adam: null\n}).value\n// == {\n//   John: false,\n//   Emma: true,\n//   Tom: true,\n//   Adam: null\n// }\n```\n\n### `DecodeOptional.keyValue`\n\nBehaves exactly as [`Decode.keyValue`](#decodekeyvalue) but decodes `null` and `undefined` as `null`:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst blackListDecoder: Decoder\u003cnull | Array\u003c\n  [string, boolean]\n\u003e\u003e = Decode.optional.keyValue(Decode.boolean)\n\nblackListDecoder.decode(null).value // == null\nblackListDecoder.decode(undefined).value // == null\nblackListDecoder.decode({\n  John: false,\n  Emma: true,\n  Tom: true\n}).value\n// == [\n//   [ 'John', false ],\n//   [ 'Emma', true ],\n//   [ 'Tom', true ]\n// ]\n```\n\nNote that `optional` statement here is assigned to `keyValue` decoder, but not to it's items, so this one will fail:\n\n```ts\nblackListDecoder.decode({\n  John: false,\n  Emma: true,\n  Tom: true,\n  Adam: null\n}).error\n// == {\n//   type: 'OPTIONAL',\n//   error: {\n//     type: 'IN_FIELD',\n//     name: 'Adam',\n//     error: { type: 'EXPECT_BOOLEAN', source: null }\n//   }\n// }\n```\n\nIf you expect both object and the items to be optional you can do it like that:\n\n```ts\nimport Decode, { Decoder } from 'decode-json'\n\nconst blackListDecoder: Decoder\u003cnull | Array\u003c\n  [string, null | boolean]\n\u003e\u003e = Decode.optional.keyValue(Decode.optional.boolean)\n\nblackListDecoder.decode(null).value // === null\nblackListDecoder.decode({\n  John: false,\n  Emma: true,\n  Tom: true,\n  Adam: null\n}).value\n// == [\n//   [ 'John', false ],\n//   [ 'Emma', true ],\n//   [ 'Tom', true ],\n//   [ 'Adam', null ]\n// ]\n```\n\n### `DecodeOptional.field`\n\nCreates an [`OptionalDecodePath`](#optionaldecodepath) instance.\n\n```ts\nimport Decode, { OptionalDecodePath } from 'decode-json'\n\nconst nameFieldDecoder: OptionalDecodePath = Decode.optional.field('name')\n```\n\n### `DecodeOptional.index`\n\nCreates an [`OptionalDecodePath`](#optionaldecodepath) instance.\n\n```ts\nimport Decode, { OptionalDecodePath } from 'decode-json'\n\nconst headDecoder: OptionalDecodePath = Decode.optional.index(0)\n```\n\n### `RequiredDecodePath`\n\nIt provides an API to build decoders for some specific path described with [`Decoder.field`](#decoderfield) and [`Decoder.index`](#decoderindex):\n\n```ts\nimport Decode from 'decode-json'\n\nconst pointDecoder = Decode.tuple(\n  Decode.field('x').float,\n  Decode.field('y').float\n)\n\nDecode.field('center').of(pointDecoder).decode([1, 2, 3]).error\n// == {\n//   type: 'EXPECT_OBJECT',\n//   source: [ 1, 2, 3 ]\n// }\n\nDecode.field('center').of(pointDecoder).decode({ name: 'John' }).error\n// == {\n//   type: 'REQUIRED_FIELD',\n//   name: 'center',\n//   source: { name: 'John' }\n// }\n\nDecode.field('center')\n  .of(pointDecoder)\n  .decode({\n    center: { x: 1.2 }\n  }).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'center',\n//   error: {\n//     type: 'REQUIRED_FIELD',\n//     name: 'y',\n//     source: { x: 1.2 }\n//   }\n// }\n\nDecode.field('center')\n  .of(pointDecoder)\n  .decode({\n    center: { x: 1.2, y: 3.4 }\n  }).value // == [ 1.2, 3.4 ]\n```\n\nThe same idea works for `RequiredDecodePath.index`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.index(0).int.decode({}).error\n// == {\n//   type: 'EXPECT_ARRAY',\n//   source: {}\n// }\n\nDecode.index(0).int.decode([]).error\n// == {\n//   type: 'REQUIRED_INDEX',\n//   position: 0,\n//   source: []\n// }\n\nDecode.index(0).int.decode([null]).error\n// == {\n//   type: 'AT_INDEX',\n//   position: 0,\n//   error: { type: 'EXPECT_INT', source: null }\n// }\n\nDecode.index(0).int.decode([42]).value // == 42\n```\n\n### `OptionalDecodePath`\n\nIt provides an API to build decoders for some specific path described with [`DecodeOptional.field`](#decodeoptionalfield) and [`DecodeOptional.index`](#decodeoptionalindex):\n\n```ts\nimport Decode from 'decode-json'\n\nconst pointDecoder = Decode.tuple(\n  Decode.field('x').float,\n  Decode.field('y').float\n)\n\nDecode.optional\n  .field('center')\n  .of(pointDecoder)\n  .decode({\n    center: { x: 1.2 }\n  }).error\n// == {\n//   type: 'OPTIONAL',\n//   error: {\n//     type: 'IN_FIELD',\n//     name: 'center',\n//     error: {\n//       type: 'REQUIRED_FIELD',\n//       name: 'y',\n//       source: { x: 1.2 }\n//     }\n//   }\n// }\n\nDecode.optional\n  .field('center')\n  .of(pointDecoder)\n  .decode({\n    center: { x: 1.2, y: 3.4 }\n  }).value // == [ 1.2, 3.4 ]\n```\n\nNote that `optional` statement here is assigned to `.field` or `.index`, so this one will fail:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.field('name').string.decode({ name: null }).error\n{\n  type: 'OPTIONAL',\n  error: {\n    type: 'IN_FIELD',\n    name: 'name',\n    error: { type: 'EXPECT_STRING', source: null }\n  }\n}\n```\n\nBut won't for this inputs:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.field('name').string.decode(null).value // == null\nDecode.optional.field('name').string.decode({}).value // == null\nDecode.optional.field('name').string.decode({ name: 'Peter' }).value // == 'Peter'\n```\n\nAnother words are `OptionalDecodePath.field` expects that object with field is optional, but not a value of the field. If you expect the value is optional too you do:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.field('name').optional.string.decode({ name: null }).value // == null\n```\n\nThe same idea works for `OptionalDecodePath.index`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.index(0).int.decode(null).value // == null\nDecode.optional.index(0).int.decode([]).value // == null\nDecode.optional.index(0).int.decode([42]).value // == 42\nDecode.optional.index(0).int.decode([null]).error\n// == {\n//   type: 'OPTIONAL',\n//   error: {\n//     type: 'AT_INDEX',\n//     position: 0,\n//     error: { type: 'EXPECT_INT', source: null }\n//   }\n// }\n\nDecode.optional.index(0).optional.int.decode([null]).value // == null\n```\n\n### `DecodeError`\n\nA set of errors describe what went wrong during decoding of unknown value with [`Decoder.decode`](#decoderdecode). The error consist of plain JavaScript data types such as strings, numbers, objects and arrays so it can be stringified to JSON without any information losses. It might be helpful for sending to tracking tools as part of report or to display friendly message in UI with [`error-to-human-readable.ts`](/src/error-to-human-readable.ts). You can always build your own functions for error formatting.\n\n#### `EXPECT_STRING`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectStringError = {\n  type: 'EXPECT_STRING'\n  source: unknown\n}\n```\n\nOccurs when `source` fails a check to be a string:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.string.decode(123).error\n// == {\n//   type: 'EXPECT_STRING',\n//   source: 123\n// }\n```\n\n#### `EXPECT_BOOLEAN`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectBooleanError = {\n  type: 'EXPECT_BOOLEAN'\n  source: unknown\n}\n```\n\nOccurs when `source` fails a check to be a boolean value:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.string.decode('I am a string').error\n// == {\n//   type: 'EXPECT_BOOLEAN',\n//   source: 'I am a string'\n// }\n```\n\n#### `EXPECT_FLOAT`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectFloatError = {\n  type: 'EXPECT_FLOAT'\n  source: unknown\n}\n```\n\nOccurs when `source` fails a check to be a float number:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.int.decode(false).error\n// == {\n//   type: 'EXPECT_FLOAT',\n//   source: false\n// }\n```\n\n#### `EXPECT_INT`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectIntError = {\n  type: 'EXPECT_INT'\n  source: unknown\n}\n```\n\nOccurs when `source` fails a check to be an integer number:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.int.decode(12.3).error\n// == {\n//   type: 'EXPECT_INT',\n//   source: 12.3\n// }\n```\n\n#### `EXPECT_EXACT`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectExactError = {\n  type: 'EXPECT_EXACT'\n  value: string | number | boolean | null\n  source: unknown\n}\n```\n\nOccurs when `source` is not equal to `value`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.exact('ADMIN').decode('admin').error\n// == {\n//   type: 'EXPECT_EXACT',\n//   value: 'ADMIN',\n//   source: 'admin'\n// }\n\nconst theWorstYear = new Date(2020, 0, 1)\n\nDecode.exact(2020, theWorstYear).decode(2021).error\n// == {\n//   type: 'EXPECT_EXACT',\n//   value: 2020,\n//   source: 2021\n// }\n```\n\n#### `EXPECT_ARRAY`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectArrayError = {\n  type: 'EXPECT_ARRAY'\n  source: unknown\n}\n```\n\nOccurs when `source` fails a check to be an array:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.list(Decode.int).decode({ title: 'NY times' }).error\n// == {\n//   type: 'EXPECT_ARRAY',\n//   source: { title: 'NY times' }\n// }\n\nDecode.index(2).boolean.decode({ name: 'Boris' }).error\n// == {\n//   type: 'EXPECT_ARRAY',\n//   source: { name: 'Boris' }\n// }\n```\n\n#### `EXPECT_OBJECT`\n\nSignature:\n\n```ts\n// not exported\ntype ExpectObjectError = {\n  type: 'EXPECT_OBJECT'\n  source: unknown\n}\n```\n\nOccurs when `source` fails a check to be an object:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.record(Decode.int).decode([1, 2, 3]).error\n// == {\n//   type: 'EXPECT_OBJECT',\n//   source: [ 1, 2, 3 ]\n// }\n\nDecode.keyValue(Decode.string).decode(`Let's rock!`).error\n// == {\n//   type: 'EXPECT_OBJECT',\n//   source: 'Let\\'s rock!'\n// }\n\nDecode.field('length').boolean.decode([true, false]).error\n// == {\n//   type: 'EXPECT_OBJECT',\n//   source: [ true, false ]\n// }\n```\n\n#### `FAILURE`\n\nSignature:\n\n```ts\n// not exported\ntype FailureError = {\n  type: 'FAILURE'\n  message: string\n  source: unknown\n}\n```\n\nOccurs either when [`Decode.fail`](#decodefail) run into decoding or when key converting in [`Decode.keyValue`](#decodekeyvalue) (or [`DecodeOptional.keyValue`](#decodeoptionalkeyvalue)) fails.\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.int\n  .chain(num =\u003e {\n    if (num \u003e 0) {\n      return Decode.succeed(num)\n    }\n\n    return Decode.fail('Expect positive integer')\n  })\n  .decode(-1).error\n// == {\n//   type: 'FAILURE',\n//   message: 'Expect positive integer',\n//   source: -1\n// }\n\nDecode.keyValue(key =\u003e {\n  const num = parseInt(key, 10)\n\n  if (!isNaN(num)) {\n    return Decode.succeed(num)\n  }\n\n  return Decode.fail('Could not convert string to integer')\n}, Decode.string).decode({\n  1: 'first',\n  2: 'second',\n  _3: 'third'\n})\n// == {\n//   type: 'FAILURE',\n//   message: 'Could not convert string to integer',\n//   source: '_3'\n// }\n```\n\n#### `REQUIRED_INDEX`\n\nSignature:\n\n```ts\n// not exported\ntype RequiredIndexError = {\n  type: 'REQUIRED_INDEX'\n  position: number\n  source: Array\u003cunknown\u003e\n}\n```\n\nOccurs when [`Decode.index`](#decodeindex) could not reach an element at a required `position` of a `source` array.\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.index(2).boolean.decode([true, false]).error\n// == {\n//   type: 'REQUIRED_INDEX',\n//   position: 2,\n//   source: [ true, false ]\n// }\n```\n\n#### `REQUIRED_FIELD`\n\nSignature:\n\n```ts\n// not exported\ntype RequiredFieldError = {\n  type: 'REQUIRED_FIELD'\n  name: string\n  source: Record\u003cstring, unknown\u003e\n}\n```\n\nOccurs when [`Decode.field`](#decodefield) could not reach a field by `name` in a `source` object.\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.field('age').int.decode({\n  id: 123,\n  name: 'Tom'\n}).error\n// == {\n//   type: 'REQUIRED_FIELD',\n//   name: 'age',\n//   source: { id: 123, name: 'Tom' }\n// }\n```\n\n#### `AT_INDEX`\n\nSignature:\n\n```ts\n// not exported\ntype AtIndexError = {\n  type: 'AT_INDEX'\n  position: number\n  error: DecodeError\n}\n```\n\nOccurs when a decoding fails with an `error` at some specific array's element at `position`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.list(Decode.int).decode([1, 2, 2.5]).error\n// == {\n//   type: 'AT_INDEX',\n//   position: 2,\n//   error: {\n//     type: 'EXPECT_INT',\n//     source: 2.5\n//   }\n// }\n\nDecode.index(2).boolean.decode([false, true, 'ok']).error\n// == {\n//   type: 'AT_INDEX',\n//   position: 2,\n//   error: {\n//     type: 'EXPECT_BOOLEAN',\n//     source: 'ok'\n//   }\n// }\n```\n\n#### `IN_FIELD`\n\nSignature:\n\n```ts\n// not exported\ntype InFieldError = {\n  type: 'IN_FIELD'\n  name: string\n  error: DecodeError\n}\n```\n\nOccurs when a decoding fails with an `error` in some specific object's field with `name`:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.record(Decode.int).decode({\n  one: 1,\n  two: 2,\n  three: '3rd'\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'three',\n//   error: {\n//     type: 'EXPECT_INT',\n//     source: '3rd'\n//   }\n// }\n\nDecode.keyValue(Decode.string).decode({\n  Russia: 'Moscow',\n  Netherlands: 'Amsterdam',\n  USA: null\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'USA',\n//   error: {\n//     type: 'EXPECT_STRING',\n//     source: null\n//   }\n// }\n\nDecode.field('is_active').boolean.decode({\n  id: 123,\n  name: 'Carl',\n  is_active: 'no'\n}).error\n// == {\n//   type: 'IN_FIELD',\n//   name: 'is_active',\n//   error: {\n//     type: 'EXPECT_BOOLEAN',\n//     source: 'no'\n//   }\n// }\n```\n\n#### `OPTIONAL`\n\nSignature:\n\n```ts\n// not exported\ntype OptionalDecodeError = {\n  type: 'OPTIONAL'\n  error: DecodeError\n}\n```\n\nIndicates that an `error` occurs for an optional path or value:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.optional.int.decode(1.23).error\n// == {\n//   type: 'OPTIONAL',\n//   error: {\n//     type: 'EXPECT_INT',\n//     source: 1.23\n//   }\n// }\n\nDecode.optional.field('lat').float.decode({\n  lng: 123.45,\n  lat: null\n}).error\n// == {\n//   type: 'OPTIONAL',\n//   error: {\n//     type: 'IN_FIELD',\n//     name: 'lat',\n//     error: {\n//       type: 'EXPECT_FLOAT',\n//       source: null\n//     }\n//   }\n// }\n```\n\n#### `ONE_OF`\n\nSignature:\n\n```ts\n// not exported\ntype OneOfError = {\n  type: 'ONE_OF'\n  errors: Array\u003cDecodeError\u003e\n}\n```\n\nOccurs when none of [`Decode.oneOf`](#decodeoneof) decoders passes with `errors` from each of the decoders:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.oneOf([\n  Decode.tuple(\n    // for coordinates as object\n    Decode.field('lat').float,\n    Decode.field('lng').float\n  ),\n\n  Decode.tuple(\n    // for coordinates as array\n    Decode.index(0).float,\n    Decode.index(1).float\n  )\n]).decode({ lat: 1.23, lon: 4.56 }).error\n// == {\n//   type: 'ONE_OF',\n//   errors: [\n//     {\n//       type: 'REQUIRED_FIELD',\n//       name: 'lng',\n//       source: { lat: 1.23, lon: 4.56 }\n//     },\n//     {\n//       type: 'EXPECT_ARRAY',\n//       source: { lat: 1.23, lon: 4.56 }\n//     }\n//   ]\n// }\n```\n\n#### `RUNTIME_EXCEPTION`\n\nSignature:\n\n```ts\ntype RuntimeException = {\n  type: 'RUNTIME_EXCEPTION'\n  error: Error\n}\n```\n\nOccurs when something unexpected happens while decoding is running so you should never worry about wrapping a decoder to `try..catch` because it does it for you automatically in runtime and TypeScript take care about correct usage during compile time.\n\n### `DecodeJsonError`\n\nA set of errors describe what went wrong during decoding of JSON string with [`Decoder.decodeJson`](#decoderdecodejson). The set is a union of [`DecodeError`](#decodeerror) with one more specific error for parse json exception.\n\n#### `INVALID_JSON`\n\nSignature:\n\n```ts\ntype RuntimeException = {\n  type: 'INVALID_JSON'\n  error: SyntaxError\n  source: string\n}\n```\n\nOccurs when [`Decoder.decodeJson`](#decoderdecodejson) tries to decode invalid JSON string:\n\n```ts\nimport Decode from 'decode-json'\n\nDecode.string.decodeJson('I am just a string').error\n// == {\n//   type: 'INVALID_JSON',\n//   error: new SyntaxError('Unexpected token I in JSON at position 0'),\n//   source: 'I am just a string'\n// }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowanturist%2Fdecode-json","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fowanturist%2Fdecode-json","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowanturist%2Fdecode-json/lists"}