All Projects → venil7 → Json Decoder

venil7 / Json Decoder

Type safe JSON decoder for TypeScript

Programming Languages

typescript
32286 projects
elm
856 projects

Projects that are alternatives of or similar to Json Decoder

Ultrajson
Ultra fast JSON decoder and encoder written in C with Python bindings
Stars: ✭ 3,504 (+5129.85%)
Mutual labels:  json, decoder
Json Rust
JSON implementation in Rust
Stars: ✭ 395 (+489.55%)
Mutual labels:  json, decoder
Lambda Talk
A Flock of Functions: Combinators, Lambda Calculus, & Church Encodings in JS
Stars: ✭ 315 (+370.15%)
Mutual labels:  composition, functional-programming
Helios
A purely functional JSON library for Kotlin built on Λrrow
Stars: ✭ 157 (+134.33%)
Mutual labels:  json, functional-programming
Djson
Fast Go decoder for dynamic JSON
Stars: ✭ 588 (+777.61%)
Mutual labels:  json, decoder
Gojay
fastest JSON encoder/decoder with powerful stream API for Golang
Stars: ✭ 2,009 (+2898.51%)
Mutual labels:  json, decoder
Scriptum
A fool's scriptum on functional programming
Stars: ✭ 346 (+416.42%)
Mutual labels:  composition, functional-programming
Not Awesome Es6 Classes
A curated list of resources on why ES6 (aka ES2015) classes are NOT awesome
Stars: ✭ 1,185 (+1668.66%)
Mutual labels:  composition, functional-programming
Bow
🏹 Bow is a cross-platform library for Typed Functional Programming in Swift
Stars: ✭ 538 (+702.99%)
Mutual labels:  composition, functional-programming
Argonaut
Purely functional JSON parser and library in scala.
Stars: ✭ 501 (+647.76%)
Mutual labels:  json, functional-programming
Forge
Functional style JSON parsing in Kotlin
Stars: ✭ 106 (+58.21%)
Mutual labels:  json, functional-programming
Redash
Tiny functional programming suite for JavaScript.
Stars: ✭ 40 (-40.3%)
Mutual labels:  composition, functional-programming
Swift Gen
🎱 Composable, transformable, controllable randomness.
Stars: ✭ 208 (+210.45%)
Mutual labels:  composition, functional-programming
Jayson
🧱 A JSON decoding/encoding library that handles optimistically or strictly.
Stars: ✭ 243 (+262.69%)
Mutual labels:  json, decoder
Functionalplus
Functional Programming Library for C++. Write concise and readable C++ code.
Stars: ✭ 1,286 (+1819.4%)
Mutual labels:  composition, functional-programming
Underscore Java
java port of Underscore.js
Stars: ✭ 327 (+388.06%)
Mutual labels:  json, functional-programming
Swift Web
🕸 A collection of Swift server-side frameworks for handling HTML, CSS, routing and middleware.
Stars: ✭ 415 (+519.4%)
Mutual labels:  composition, functional-programming
Bugz
🐛 Composable User Agent Detection using Ramda
Stars: ✭ 15 (-77.61%)
Mutual labels:  composition, functional-programming
Funcj
Assorted functional-oriented data structures and algorithms for Java.
Stars: ✭ 60 (-10.45%)
Mutual labels:  json, functional-programming
Grules
A simple, but expandable, rules engine for Go
Stars: ✭ 65 (-2.99%)
Mutual labels:  json

TypeScript JSON Decoder: 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 aische/JsonDecoder but is a full rewrite, and does not rely on unsafe any type.

Build Status TypeScript

Give us a 🌟on Github

Compositional decoding

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.

Install (npm or yarn)

  $> npm install json-decoder
  $> yarn add json-decoder

Basic decoders

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 inference

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

API

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.

  • bind<T2>(bindFunc: (t: T) => Decoder<T2>): Decoder<T2> - allows for monadic (think >>=) chaining of decoders. Takes a function, that given a result of previous decoding return a new decoder of type Decoder<T2>.

  • then<T2>(nextDecoder: Decoder<T2>): Decoder<T2> - allows to chain several decoders one after the other, is an equivalent of calling allOfDecoders(thisDecoder, nextDecoder)

Custom decoder

Customized decoders are possible by combining existing decoders with user defined mapping. For example to create a floatDecoder that decodes valid string:

const floatDecoder = stringDecoder.map(parseFloat);
const float = floatDecoder.decode("123.45"); //Ok(123.45)

Result and pattern matching

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 value
  • Err<T> - carries an unsuccesfull decoding result of type T, use .message to access error message

Result 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
}

Friendly errors

Errors emit exact decoder expectations where decoding whent wrong, even for deeply nested objects and arrays

Mapping and type conversion

  • simple type converson - is possible with .map and chaining decoder, see floatDecoder as an example
  • 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.
  const decoder = oneOfDecoders<string | number>(
      stringDecoder,
      numberDecoder
    ).bind<string | number>((t: string | number) =>
      typeof t == "string"
        ? stringDecoder.map((s) => `${s}!!`)
        : numberDecoder.map((n) => n * 2)
    );

Validation

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.

Example: integerDecoder - only decodes an integer and fails on a float value

const 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")

Example: emailDecoder - only decodes a string that matches email regex, fails otherwise

const emailDecoder: Decoder<number> = stringDecoder.validate(/^\[email protected]\S+$/.test, "not an email");
const email = emailDecoder.decode("[email protected]"); //Ok("[email protected]")
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.

Example: emailDecoder - only decodes a string that matches email regex, fails otherwise

const emailDecoder: Decoder<number> = stringDecoder.validate(/^\[email protected]\S+$/.test, (invalidEmail) => `${invalidEmail} not an email`);
const email = emailDecoder.decode("[email protected]"); //Ok("[email protected]")
const notEmail = emailDecoder.decode("joe"); //Err("joe is not an email")

Contributions are welcome

Please raise an issue or create a PR

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].