{"id":15059532,"url":"https://github.com/venil7/json-decoder","last_synced_at":"2025-07-14T13:08:48.664Z","repository":{"id":35003939,"uuid":"195525226","full_name":"venil7/json-decoder","owner":"venil7","description":"Type safe JSON decoder for TypeScript","archived":false,"fork":false,"pushed_at":"2023-10-19T10:42:13.000Z","size":2149,"stargazers_count":76,"open_issues_count":3,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-23T02:49:45.734Z","etag":null,"topics":["composition","decoder","elm","elm-lang","functional-programming","json","typescript"],"latest_commit_sha":null,"homepage":"https://darkruby.co.uk/json-decoder/#/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/venil7.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-07-06T10:07:23.000Z","updated_at":"2023-11-27T01:04:01.000Z","dependencies_parsed_at":"2024-01-07T01:16:32.306Z","dependency_job_id":"f5d07b94-80b8-4f78-95f7-2c2116641658","html_url":"https://github.com/venil7/json-decoder","commit_stats":{"total_commits":35,"total_committers":6,"mean_commits":5.833333333333333,"dds":0.6,"last_synced_commit":"34476a2d3f41d6ddd33ac9698356fbb2d17ce2c5"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/venil7/json-decoder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venil7%2Fjson-decoder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venil7%2Fjson-decoder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venil7%2Fjson-decoder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venil7%2Fjson-decoder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/venil7","download_url":"https://codeload.github.com/venil7/json-decoder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/venil7%2Fjson-decoder/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265297041,"owners_count":23742585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["composition","decoder","elm","elm-lang","functional-programming","json","typescript"],"created_at":"2024-09-24T22:45:12.798Z","updated_at":"2025-07-14T13:08:48.619Z","avatar_url":"https://github.com/venil7.png","language":"TypeScript","funding_links":[],"categories":["CSS In JS With Types"],"sub_categories":["Runtime"],"readme":"# TypeScript JSON Decoder: `json-decoder`\n\n**`json-decoder`** is a type safe compositional JSON decoder for `TypeScript`. It is heavily inspired by [Elm](https://package.elm-lang.org/packages/elm/json/latest/) and [ReasonML](https://github.com/glennsl/bs-json) JSON decoders. The code is loosely based on [aische/JsonDecoder](https://github.com/aische/JsonDecoder) but is a full rewrite, and does not rely on unsafe `any` type.\n\n[![Build Status](https://travis-ci.org/venil7/json-decoder.svg?branch=master)](https://travis-ci.org/venil7/json-decoder) [![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://github.com/ellerbrock/typescript-badges/)\n\nGive us a 🌟on Github\n\n## Compositional decoding\n\nThe decoder comprises of small basic building blocks (listed below), that can be composed into JSON decoders of any complexity, including deeply nested structures, heterogenous arrays, etc. If a type can be expressed as `TypeScript` `interface` or `type` (including algebraic data types) - it can be safely decoded and type checked with `json-decoder`.\n\n## Install (npm or yarn)\n\n```\n  $\u003e npm install json-decoder\n  $\u003e yarn add json-decoder\n```\n\n## Basic decoders\n\nBelow is a list of basic decoders supplied with `json-decoder`:\n\n- `stringDecoder` - decodes a string:\n\n  ```TypeScript\n  const result: Result\u003cstring\u003e = stringDecoder.decode(\"some string\"); //Ok(\"some string\");\n  const result: Result\u003cstring\u003e = stringDecoder.decode(123.45); //Err(\"string expected\");\n  ```\n\n- `numberDecoder` - decodes a number:\n\n  ```TypeScript\n  const result: Result\u003cnumber\u003e = numberDecoder.decode(123.45); //Ok(123.45);\n  const result: Result\u003cnumber\u003e = numberDecoder.decode(\"some string\"); //Err(\"number expected\");\n  ```\n\n- `boolDecoder` - decodes a boolean:\n\n  ```TypeScript\n  const result: Result\u003cboolean\u003e = boolDecoder.decode(true); //Ok(true);\n  const result: Result\u003cboolean\u003e = boolDecoder.decode(null); //Err(\"bool expected\");\n  ```\n\n- `nullDecoder` - decodes a `null` value:\n\n  ```TypeScript\n  const result: Result\u003cnull\u003e = nullDecoder.decode(null); //Ok(null);\n  const result: Result\u003cnull\u003e = boolDecoder.decode(false); //Err(\"null expected\");\n  ```\n\n- `undefinedDecoder` - decodes an `undefined` value:\n\n  ```TypeScript\n  const result: Result\u003cundefined\u003e = undefinedDecoder.decode(undefined); //Ok(undefined);\n  const result: Result\u003cundefined\u003e = boolDecoder.decode(null); //Err(\"undefined expected\");\n  ```\n\n- `arrayDecoder\u003cT\u003e(decoder: Decoder\u003cT\u003e)` - decodes an array, requires one parameter of array item decoder:\n\n  ```TypeScript\n  const numberArrayDecoder = arrayDecoder(numberDecoder);\n  const result: Result\u003cnumber[]\u003e = numberArrayDecoder.decode([1,2,3]); //Ok([1,2,3]);\n  const result: Result\u003cnumber[]\u003e = numberArrayDecoder.decode(\"some string\"); //Err(\"array expected\");\n  const result: Result\u003cnumber[]\u003e = numberArrayDecoder.decode([true, false, null]); //Err(\"array: number expected\");\n  ```\n\n- `objectDecoder\u003cT\u003e(decoderMap: DecoderMap\u003cT\u003e)` - decodes an object, requires a decoder map parameter. Decoder map is a composition of decoders, one for each field of an object, that themselves can be object decoders if neccessary.\n\n  ```TypeScript\n  type Pet = {name: string, age: number};\n  const petDecoder = objectDecoder\u003cPet\u003e({\n    name: stringDecoder,\n    age: numberDecoder,\n  });\n  const result: Result\u003cPet\u003e = petDecoder.decode({name: \"Varia\", age: 0.5}); //Ok({name: \"Varia\", age: 0.5});\n  const result: Result\u003cPet\u003e = petDecoder.decode({name: \"Varia\", type: \"cat\"}); //Err(\"name: string expected\");\n\n  const petDecoder = objectDecoder\u003cPet\u003e({\n    name: stringDecoder,\n    type: stringDecoder, //\u003c-- error: field type is not defined in Pet\n  });\n  ```\n\n- `exactDecoder\u003cT\u003e(value: T)` - decodes a value that is passed as a parameter. Any other value will result in `Err`:\n\n  ```TypeScript\n  const catDecoder = exactDecoder(\"cat\");\n  const result: Result\u003c\"cat\"\u003e = catDecoder.decode(\"cat\"); //Ok(\"cat\");\n  const result: Result\u003c\"cat\"\u003e = catDecoder.decode(\"dog\"); //Err(\"cat expected\");\n  ```\n\n- `oneOfDecoders\u003cT1|T2...Tn\u003e(...decoders: Decoder\u003cT1|T2...Tn\u003e[])` - takes a number decoders as parameter and tries to decode a value with each in sequence, returns as soon as one succeeds, errors otherwise. Useful for algebraic data types.\n\n  ```TypeScript\n  const catDecoder = exactDecoder(\"cat\");\n  const dogDecoder = exactDecoder(\"dog\");\n  const petDecoder = oneOfDecoders\u003c\"cat\"|\"dog\"\u003e = oneOfDecoders(catDecoder, dogDecoder);\n\n  const result: Result\u003c\"cat\"|\"dog\"\u003e = petDecoder.decode(\"cat\"); //Ok(\"cat\");\n  const result: Result\u003c\"cat\"|\"dog\"\u003e = petDecoder.decode(\"dog\"); //Ok(\"dog\");\n  const result: Result\u003c\"cat\"|\"dog\"\u003e = petDecoder.decode(\"giraffe\"); //Err(\"none of decoders matched\");\n  ```\n\n- `allOfDecoders(...decoders: Decoder\u003cT1|T2...Tn\u003e[]): Decoder\u003cTn\u003e` - takes a number decoders as parameter and tries to decode a value with each in sequence, all decoders have to succeed. If at leat one defocer fails - returns `Err`.\n\n  ```TypeScript\n  const catDecoder = exactDecoder(\"cat\");\n  const result: Result\u003c\"cat\"\u003e = allOfDecoders(stringSecoder, catDecoder); //Ok(\"cat\")\n  ```\n\n## Type inference\n\nType works both ways - not only you can specify type for a decoder, it is also possible to infer the type from an existing decoder, particularly useful for composition of decoders:\n\n```TypeScript\ntype Number = DecoderType\u003ctypeof numberDecoder\u003e; //number\nconst someDecoder = objectDecoder({\n  field1: stringDecoder,\n  field2: numberDecoder,\n  field3: arrayDecoder(numberDecoder)\n});\ntype Some = DecoderType\u003ctypeof someDecoder\u003e; // {field1: string, field2: number, field3: number[] }\nconst some: Some = await someDecoder.decodeAsync({...});\n\nconst stringOrNumberDecoder = oneOfDecoders\u003cstring |number\u003e(stringDecoder, numberDecoder);\ntype StringOrNumber = DecoderType\u003ctypeof stringOrNumberDecoder\u003e; //string | number\n```\n\n## API\n\nEach decoder has the following methods:\n\n- `decode(json:unknown): Result\u003cT\u003e` - attempts to decode a value of `unknown` type. Returns `Ok\u003cT\u003e` if succesful, `Err\u003cT\u003e` otherwise.\n- `decodeAsync(json:unknown): Promise\u003cT\u003e` - Returns a `Promise\u003cT\u003e` that attempts to decode a value of `unknown` type. Resolves with `T` if succesful, rejects `Error{message:string}` otherwise.\n  A typical usage of this would be in an `async` function context:\n\n  ```TypeScript\n  const getPet = async (): Promise\u003cPet\u003e =\u003e {\n    const result = await fetch(\"http://some.pet.api/cat/1\");\n    const pet: Pet = await petDecoder.decodeAsync(await result.json());\n    return pet;\n  };\n  ```\n\n- `map(func: (t: T) =\u003e T2): Decoder\u003cT2\u003e` - each decoder is a [functor](https://wiki.haskell.org/Functor). `Map` allows you to apply a function to an underlying decoder value, provided that decoding succeeded. Map accepts a function of type `(t: T) -\u003e T2`, where `T` is a type of decoder (and underlying value), and `T2` is a type of resulting decoder.\n\n- `bind\u003cT2\u003e(bindFunc: (t: T) =\u003e Decoder\u003cT2\u003e): Decoder\u003cT2\u003e` - allows for [monadic](https://wiki.haskell.org/Monad) (think \u003e\u003e=) chaining of decoders. Takes a function, that given a result of previous decoding return a new decoder of type `Decoder\u003cT2\u003e`.\n\n- `then\u003cT2\u003e(nextDecoder: Decoder\u003cT2\u003e): Decoder\u003cT2\u003e` - allows to chain several decoders one after the other, is an equivalent of calling `allOfDecoders(thisDecoder, nextDecoder)`\n\n## Custom decoder\n\nCustomized decoders are possible by combining existing decoders with user defined mapping. For example to create a `floatDecoder` that decodes valid string:\n\n```TypeScript\nconst floatDecoder = stringDecoder.map(parseFloat);\nconst float = floatDecoder.decode(\"123.45\"); //Ok(123.45)\n\n```\n\n## Result and pattern matching\n\nDecoding can either succeed or fail, to denote that `json-decoder` has [ADT](https://en.wikipedia.org/wiki/Algebraic_data_type) type `Result\u003cT\u003e`, which can take two forms:\n\n- `Ok\u003cT\u003e` - carries a succesfull decoding result of type `T`, use `.value` to access value\n- `Err\u003cT\u003e` - carries an unsuccesfull decoding result of type `T`, use `.message` to access error message\n\n`Result` also has functorial `map` function that allows to apply a function to a value, provided that it exists\n\n```TypeScript\nconst r: Result\u003cstring\u003e = Ok(\"cat\").map(s =\u003e s.toUpperCase()); //Ok(\"CAT\")\nconst e: Result\u003cstring\u003e = Err(\"some error\").map(s =\u003e s.toUpperCase()); //Err(\"some error\")\n```\n\nIt is possible to pattern-match (using poor man's pattern matching provided by TypeScript) to determite the type of `Result`\n\n```TypeScript\n// assuming some result:Result\u003cPerson\u003e\n\nswitch (result.type) {\n  case OK: result.value; // Person\n  case Err: result.message; // message string\n}\n```\n\n## Friendly errors\n\nErrors emit exact decoder expectations where decoding whent wrong, even for deeply nested objects and arrays\n\n## Mapping and type conversion\n\n- **simple type converson** - is possible with `.map` and chaining decoder, see `floatDecoder` as an example\n- **more comlex conditional** decoding is possible using `.bind` to chain decoders one after the other, with user defined arbitrary combination logic. The following example executes different decoder depending on the result of previous decoder.\n\n```TypeScript\n  const decoder = oneOfDecoders\u003cstring | number\u003e(\n      stringDecoder,\n      numberDecoder\n    ).bind\u003cstring | number\u003e((t: string | number) =\u003e\n      typeof t == \"string\"\n        ? stringDecoder.map((s) =\u003e `${s}!!`)\n        : numberDecoder.map((n) =\u003e n * 2)\n    );\n```\n\n## Validation\n\n`JSON` only exposes an handful of types: `string`, `number`, `null`, `boolean`, `array` and `object`. There's no way to enforce special kind of validation on any of above types using just `JSON`. `json-decoder` allows to validate values against a predicate.\n\n#### Example: `integerDecoder` - only decodes an integer and fails on a float value\n\n```TypeScript\nconst integerDecoder: Decoder\u003cnumber\u003e = numberDecoder.validate(n =\u003e Math.floor(n) === n, \"not an integer\");\nconst integer = integerDecoder.decode(123); //Ok(123)\nconst float = integerDecoder.decode(123.45); //Err(\"not an integer\")\n```\n\n#### Example: `emailDecoder` - only decodes a string that matches email regex, fails otherwise\n\n```TypeScript\nconst emailDecoder: Decoder\u003cnumber\u003e = stringDecoder.validate(/^\\S+@\\S+$/.test, \"not an email\");\nconst email = emailDecoder.decode(\"joe@example.com\"); //Ok(\"joe@example.com\")\nconst notEmail = emailDecoder.decode(\"joe\"); //Err(\"not an email\")\n```\n\nAlso `decoder.validate` can take function as a second parameter. It should have such type: `(value: T) =\u003e string`.\n\n#### Example: `emailDecoder` - only decodes a string that matches email regex, fails otherwise\n\n```TypeScript\nconst emailDecoder: Decoder\u003cnumber\u003e = stringDecoder.validate(/^\\S+@\\S+$/.test, (invalidEmail) =\u003e `${invalidEmail} not an email`);\nconst email = emailDecoder.decode(\"joe@example.com\"); //Ok(\"joe@example.com\")\nconst notEmail = emailDecoder.decode(\"joe\"); //Err(\"joe is not an email\")\n```\n\n## Contributions are welcome\n\nPlease raise an issue or create a PR\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvenil7%2Fjson-decoder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvenil7%2Fjson-decoder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvenil7%2Fjson-decoder/lists"}