Socket
Socket
Sign inDemoInstall

json-decoder

Package Overview
Dependencies
0
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    json-decoder

Lightweight, lightning-fast, type safe JSON decoder for TypeScript


Version published
Weekly downloads
167
increased by25.56%
Maintainers
1
Created
Weekly downloads
 

Readme

Source

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 ts.data.json 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:

    let result: Result<string> = stringDecoder.decode("some string"); //Ok("some string");
    let result: Result<string> = stringDecoder.decode(123.45); //Err("string expected");
    
  • numberDecoder - decodes a number:

    let result: Result<number> = numberDecoder.decode(123.45); //Ok(123.45);
    let result: Result<number> = numberDecoder.decode("some string"); //Err("number expected");
    
  • boolDecoder - decodes a boolean:

    let result: Result<boolean> = boolDecoder.decode(true); //Ok(true);
    let result: Result<boolean> = boolDecoder.decode(null); //Err("bool expected");
    
  • nullDecoder - decodes a null value:

    let result: Result<null> = nullDecoder.decode(null); //Ok(null);
    let result: Result<null> = boolDecoder.decode(false); //Err("null expected");
    
  • undefinedDecoder - decodes an undefined value:

    let result: Result<null> = nullDecoder.decode(undefined); //Ok(undefined);
    let result: Result<null> = boolDecoder.decode(null); //Err("undefined expected");
    
  • arrayDecoder<T>(decoder: Decoder<T>) - decodes an array, requires one parameter of array item decoder:

    let result: Result<number[]> = arrayDecoder.decode([1,2,3]); //Ok([1,2,3]);
    let result: Result<number[]> = arrayDecoder.decode("some string"); //Err("array expected");
    let result: Result<number[]> = arrayDecoder.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};
    let petDecoder = objectDecoder<Person>({
      name: stringDecoder,
      age: numberDecoder,
    });
    let result: Result<Pet> = petDecoder.decode({name: "Varia", age: 0.5}); //Ok({name: "Varia", age: 0.5});
    let result: Result<Pet> = petDecoder.decode({name: "Varia", type: "cat"}); //Err("name: string expected");
    
    let petDecoder = objectDecoder<Person>({
      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:

    let catDecoder = exactDecoder("cat");
    let result: Result<"cat"> = catDecoder.decode("cat"); //Ok("cat");
    let 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.

    let catDecoder = exactDecoder("cat");
    let dogDecoder = exactDecoder("dog");
    let petDecoder = oneOfDecoders<"cat"|"dog"> = oneOfDecoders(catDecoder, dogDecoder);
    
    let result: Result<"cat"|"dog"> = petDecoder.decode("cat"); //Ok("cat");
    let result: Result<"cat"|"dog"> = petDecoder.decode("dog"); //Ok("dog");
    let 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.

    let catDecoder = exactDecoder("cat");
    let result: Result<"cat"> = allOfDecoders(stringSecoder, catDecoder); //Ok("cat")
    

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 deocoder 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 monading chaining of decoders. Takes a function, that returns a Decoder<T2>, and returns a Decoder<T2>

Custom decoder

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 decodign 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

let r:Result<string> = Ok("cat").map(s => s.toUpperCase); //Ok("CAT")
let 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

TBC

Mapping and type conversion

TBC

Validation

JSON only exposes an handful of types: string, number, null, boolean, array and object. There's no way t enforce special kind of validation on ny 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
let integerDecoder : Decoder<number> = numberDecoder.validate(n => Math.floor(n) === n, "not an integer");
let integer = integerDecoder.decode(123); //Ok(123)
let float = integerDecoder.decode(123.45); //Err("not an integer")

Example: emailDecoder - only decodes a string that matches email regex, fails otherwise
let emailDecoder : Decoder<number> = stringDecoder.validate(/^\S+@\S+$/.test, "not an email");
let integer = emailDecoder.decode("joe@example.com"); //Ok("joe@example.com")
let float = emailDecoder.decode("joe"); //Err("not an email")

Contributions are welcome

Please raise an issue or create a PR

Keywords

FAQs

Last updated on 23 Oct 2019

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc