
Security News
Open Source Maintainers Feeling the Weight of the EU’s Cyber Resilience Act
The EU Cyber Resilience Act is prompting compliance requests that open source maintainers may not be obligated or equipped to handle.
json-decoder
Advanced tools
json-decoder
json-decoder
is a type safe compositional JSON decoder for TypeScript
. It is heavily inspired by Elm and ReasonML JSON decoders. The code is loosely based on ts.data.json but is a full rewrite, and does not rely on unsafe any
type.
Give us a 🌟on Github
The 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
.
$> npm install json-decoder
$> yarn add json-decoder
Below is a list of basic decoders supplied with json-decoder
:
stringDecoder
- decodes a string:
const result: Result<string> = stringDecoder.decode("some string"); //Ok("some string");
const result: Result<string> = stringDecoder.decode(123.45); //Err("string expected");
numberDecoder
- decodes a number:
const result: Result<number> = numberDecoder.decode(123.45); //Ok(123.45);
const result: Result<number> = numberDecoder.decode("some string"); //Err("number expected");
boolDecoder
- decodes a boolean:
const result: Result<boolean> = boolDecoder.decode(true); //Ok(true);
const result: Result<boolean> = boolDecoder.decode(null); //Err("bool expected");
nullDecoder
- decodes a null
value:
const result: Result<null> = nullDecoder.decode(null); //Ok(null);
const result: Result<null> = boolDecoder.decode(false); //Err("null expected");
undefinedDecoder
- decodes an undefined
value:
const result: Result<undefined> = undefinedDecoder.decode(undefined); //Ok(undefined);
const result: Result<undefined> = boolDecoder.decode(null); //Err("undefined expected");
arrayDecoder<T>(decoder: Decoder<T>)
- decodes an array, requires one parameter of array item decoder:
const numberArrayDecoder = arrayDecoder(numberDecoder);
const result: Result<number[]> = numberArrayDecoder.decode([1,2,3]); //Ok([1,2,3]);
const result: Result<number[]> = numberArrayDecoder.decode("some string"); //Err("array expected");
const result: Result<number[]> = numberArrayDecoder.decode([true, false, null]); //Err("array: number expected");
objectDecoder<T>(decoderMap: DecoderMap<T>)
- 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.
type Pet = {name: string, age: number};
const petDecoder = objectDecoder<Pet>({
name: stringDecoder,
age: numberDecoder,
});
const result: Result<Pet> = petDecoder.decode({name: "Varia", age: 0.5}); //Ok({name: "Varia", age: 0.5});
const result: Result<Pet> = petDecoder.decode({name: "Varia", type: "cat"}); //Err("name: string expected");
const petDecoder = objectDecoder<Pet>({
name: stringDecoder,
type: stringDecoder, //<-- error: field type is not defined in Pet
});
exactDecoder<T>(value: T)
- decodes a value that is passed as a parameter. Any other value will result in Err
:
const catDecoder = exactDecoder("cat");
const result: Result<"cat"> = catDecoder.decode("cat"); //Ok("cat");
const result: Result<"cat"> = catDecoder.decode("dog"); //Err("cat expected");
oneOfDecoders<T1|T2...Tn>(...decoders: Decoder<T1|T2...Tn>[])
- 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.
const catDecoder = exactDecoder("cat");
const dogDecoder = exactDecoder("dog");
const petDecoder = oneOfDecoders<"cat"|"dog"> = oneOfDecoders(catDecoder, dogDecoder);
const result: Result<"cat"|"dog"> = petDecoder.decode("cat"); //Ok("cat");
const result: Result<"cat"|"dog"> = petDecoder.decode("dog"); //Ok("dog");
const result: Result<"cat"|"dog"> = petDecoder.decode("giraffe"); //Err("none of decoders matched");
allOfDecoders(...decoders: Decoder<T1|T2...Tn>[]): Decoder<Tn>
- 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
.
const catDecoder = exactDecoder("cat");
const result: Result<"cat"> = allOfDecoders(stringSecoder, catDecoder); //Ok("cat")
Type 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:
type Number = DecoderType<typeof numberDecoder>; //number
const someDecoder = objectDecoder({
field1: stringDecoder,
field2: numberDecoder,
field3: arrayDecoder(numberDecoder)
});
type Some = DecoderType<typeof someDecoder>; // {field1: string, field2: number, field3: number[] }
const some: Some = await someDecoder.decodeAsync({...});
const stringOrNumberDecoder = oneOfDecoders<string |number>(stringDecoder, numberDecoder);
type StringOrNumber = DecoderType<typeof stringOrNumberDecoder>; //string | number
Each decoder has the following methods:
decode(json:unknown): Result<T>
- attempts to decode a value of unknown
type. Returns Ok<T>
if succesful, Err<T>
otherwise.
decodeAsync(json:unknown): Promise<T>
- Returns a Promise<T>
that attempts to decode a value of unknown
type. Resolves with T
if succesful, rejects Error{message:string}
otherwise.
A typical usage of this would be in an async
function context:
const getPet = async (): Promise<Pet> => {
const result = await fetch("http://some.pet.api/cat/1");
const pet: Pet = await petDecoder.decodeAsync(await result.json());
return pet;
};
map(func: (t: T) => T2): Decoder<T2>
- each decoder is a 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) -> T2
, where T
is a type of decoder (and underlying value), and T2
is a type of resulting decoder.
then(bindFunc: (t: T) => Decoder<T2>): Decoder<T2>
- allows for monadic chaining of decoders. Takes a function, that returns a Decoder<T2>
, and returns a Decoder<T2>
Decoding can either succeed or fail, to denote that json-decoder
has ADT type Result<T>
, which can take two forms:
Ok<T>
- carries a succesfull decoding result of type T
, use .value
to access valueErr<T>
- carries an unsuccesfull decoding result of type T
, use .message
to access error messageResult
also has functorial map
function that allows to apply a function to a value, provided that it exists
const r: Result<string> = Ok("cat").map(s => s.toUpperCase()); //Ok("CAT")
const e: Result<string> = Err("some error").map(s => s.toUpperCase()); //Err("some error")
It is possible to pattern-match (using poor man's pattern matching provided by TypeScript) to determite the type of Result
// assuming some result:Result<Person>
switch (result.type) {
case OK: result.value; // Person
case Err: result.message; // message string
}
TBC
TBC
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.
integerDecoder
- only decodes an integer and fails on a float valueconst integerDecoder: Decoder<number> = numberDecoder.validate(n => Math.floor(n) === n, "not an integer");
const integer = integerDecoder.decode(123); //Ok(123)
const float = integerDecoder.decode(123.45); //Err("not an integer")
emailDecoder
- only decodes a string that matches email regex, fails otherwiseconst emailDecoder: Decoder<number> = stringDecoder.validate(/^\S+@\S+$/.test, "not an email");
const email = emailDecoder.decode("joe@example.com"); //Ok("joe@example.com")
const notEmail = emailDecoder.decode("joe"); //Err("not an email")
Also decoder.validate
can take function as a second parameter. It should have such type: (value: T) => string
.
emailDecoder
- only decodes a string that matches email regex, fails otherwiseconst emailDecoder: Decoder<number> = stringDecoder.validate(/^\S+@\S+$/.test, (invalidEmail) => `${invalidEmail} not an email`);
const email = emailDecoder.decode("joe@example.com"); //Ok("joe@example.com")
const notEmail = emailDecoder.decode("joe"); //Err("joe is not an email")
Please raise an issue or create a PR
FAQs
Lightweight, lightning-fast, type safe JSON decoder for TypeScript
The npm package json-decoder receives a total of 353 weekly downloads. As such, json-decoder popularity was classified as not popular.
We found that json-decoder demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The EU Cyber Resilience Act is prompting compliance requests that open source maintainers may not be obligated or equipped to handle.
Security News
Crates.io adds Trusted Publishing support, enabling secure GitHub Actions-based crate releases without long-lived API tokens.
Research
/Security News
Undocumented protestware found in 28 npm packages disrupts UI for Russian-language users visiting Russian and Belarusian domains.