A JSON decoding library for Typescript
Clone or download
Pull request Compare This branch is 29 commits ahead of aische:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github
.vscode
src
.editorconfig
.gitignore
.npmignore
.travis.yml
CHANGELOG.md
LICENSE
README.md
package.json
tsconfig.json

README.md

JsonDecoder

Build Status npm version Conventional Commits

Typescript type annotations give us compile-time guarantees, but at run-time, when data flows from the server to our clients, lots of things can go wrong.

JSON decoders validate the JSON before it comes into our program. So if the data has an unexpected structure, we learn about it immediately.

If you are new to JSON decoding, you may want to read the introductory article Decoding JSON with Typescript about why and how to use this library.

Install

npm install ts.data.json --save

Example

type User = {
  firstname: string;
  lastname: string;
};

const userDecoder = JsonDecoder.object<User>(
  {
    firstname: JsonDecoder.string,
    lastname: JsonDecoder.string
  },
  'User'
);

const jsonObjectOk = {
  firstname: 'Damien',
  lastname: 'Jurado'
};

userDecoder
  .decodePromise(jsonObjectOk)
  .then(user => {
    console.log(`User ${user.firstname} ${user.lastname} decoded successfully`);
  })
  .catch(error => {
    console.log(error);
  });

// Output: User Damien Jurado decoded successfully

const jsonObjectKo = {
  firstname: 'Erik',
  lastname: null
};

userDecoder
  .decodePromise(jsonObjectKo)
  .then(user => {
    console.log('User decoded successfully');
  })
  .catch(error => {
    console.error(error);
  });

// Output: <User> decoder failed at key "lastname" with error: null is not a valid string

API

JsonDecoder.string

string: Decoder<string>

Creates a string decoder.

JsonDecoder.string.decode('hi'); // Ok<string>({value: 'hi'})
JsonDecoder.string.decode(5); // Err({error: '5 is not a valid string'})

JsonDecoder.number

number: Decoder<number>

Creates a number decoder.

JsonDecoder.number.decode(99); // Ok<number>({value: 99})
JsonDecoder.string.decode('hola'); // Err({error: 'hola is not a valid number'})

JsonDecoder.boolean

boolean: Decoder<boolean>

Creates a boolean decoder.

JsonDecoder.boolean.decode(true); // Ok<boolean>({value: true})
JsonDecoder.boolean.decode(null); // Err({error: 'null is not a valid boolean'})

JsonDecoder.object

object<a>(decoders: DecoderObject<a>, decoderName: string, keyMap?: DecoderObjectKeyMap<a>): Decoder<a>

Creates an object decoder.

@param decoders: DecoderObject<a>

Key/value pair that has to comply with the <a> type.

@param decoderName: string

The type of the object we are decoding. i.e. User. It is used to generate meaningful decoding error messages.

@param keyMap?: DecoderObjectKeyMap<a>

Optional key/value pair to map JSON-land keys with Model-land keys. Useful when the JSON keys don't match with the decoded type keys.

Basic example

type User = {
  firstname: string;
  lastname: string;
};
const userDecoder = JsonDecoder.object<User>(
  {
    firstname: JsonDecoder.string,
    lastname: JsonDecoder.string
  },
  'User'
);

const jsonOk = {
  firstname: 'Damien',
  lastname: 'Jurado'
};
userDecoder.decode(jsonOk);
// Output: Ok<User>({value: {firstname: 'Damien', lastname: 'Jurado'}})

const jsonKo = {
  firstname: null,
  lastname: 'Satie'
};
userDecoder.decode(jsonKo);
// Output: Err({error: '<User> decoder failed at key "firstname" with error: null is not a valid string'})

keyMap example

const userDecoder = JsonDecoder.object<User>(
  {
    firstname: JsonDecoder.string,
    lastname: JsonDecoder.string
  },
  'User',
  {
    firstname: 'fName',
    lastname: 'lName'
  }
);

const jsonOk = {
  fName: 'Nick',
  lName: 'Drake'
};
userDecoder.decode(json);
// Output: Ok({value: {firstname: 'Nick', lastname: 'Drake'}})

const jsonKo = {
  fName: 'Nick'
};
userDecoder.decode(json);
// Output: Err({error: '<User> decoder failed at key "lastname" (mapped from the JSON key "lName") with error: undefined is not a valid string'})

JsonDecoder.array

array<a>(decoder: Decoder<a>, decoderName: string): Decoder<Array<a>>

Creates an array decoder.

@param decoder: Decoder<a>

The decoder used to decode every Array<a> item.

@param decoderName: string

The type of the object we are decoding. i.e. User[]. It is used to generate meaningful decoding error messages.

JsonDecoder.array<number>(JsonDecoder.number, 'number[]').decode([1, 2, 3]);
// Output: Ok<number[]>({value: [1, 2, 3]})

JsonDecoder.array<number>(JsonDecoder.number, 'number[]').decode([1, '2', 3]);
// Output: Err({error: '<number[]> decoder failed at index 1 with error: "2" is not a valid number'})

JsonDecoder.dictionary

dictionary<a>(decoder: Decoder<a>, decoderName: string): Decoder<{ [name: string]: a }>

Creates a dictionary decoder.

@param decoder: Decoder<a>

The decoder used to decode every value of the key/value pairs.

@param decoderName: string

The type of the object we are decoding. i.e. User. It is used to generate meaningful decoding error messages.

JsonDecoder.dictionary(JsonDecoder.number, 'Dict<number>').decode({
  a: 1,
  b: 2
});
// Output: Ok<Dict<number>>({value: {a: 1, b: 2}})

JsonDecoder.dictionary(JsonDecoder.number, 'Dict<number>').decode({
  a: 1,
  b: 2,
  c: null
});
// Output: Err({error: '<Dict<number>> dictionary decoder failed at key "c" with error: null is not a valid number'})

JsonDecoder.oneOf

oneOf<a>(decoders: Array<Decoder<a>>, decoderName: string): Decoder<a>

The oneOf decoder tries to decode the provided JSON with a collection of decoders. It returns Ok with the first successful decoded value or Err if all decoders fail.

@param decoders: Array<Decoder<a>>

The array of possible decoders that the JSON can be decoded with.

@param decoderName: string

The type of the object we are decoding. i.e. number | string. It is used to generate meaningful decoding error messages.

JsonDecoder.oneOf<string | number>(
  [JsonDecoder.string, JsonDecoder.number],
  'string | number'
).decode(1);
// Output: Ok<string | number>({value: 1})

JsonDecoder.oneOf<string | number>(
  [JsonDecoder.string, JsonDecoder.number],
  'string | number'
).decode(true);
// Output: Err({error: "<string | number> decoder failed because true can't be decoded with any of the provided oneOf decoders"})

JsonDecoder.lazy

lazy<a>(mkDecoder: () => Decoder<a>): Decoder<a>

Decoder for recursive data structures.

@param mkDecoder: () => Decoder<a>

Function that returns a decoder.

type Node<a> = {
  value: a;
  children?: Node<a>[];
};
const treeDecoder: JsonDecoder.Decoder<Node<string>> = JsonDecoder.object<
  Node<string>
>(
  {
    value: JsonDecoder.string,
    children: JsonDecoder.oneOf<Node<string>[]>(
      [
        JsonDecoder.lazy(() => JsonDecoder.array(treeDecoder, 'Node<a>[]')),
        JsonDecoder.isUndefined([])
      ],
      'Node<string>[] | isUndefined'
    )
  },
  'Node<string>'
);
treeDecoder.decode({
  value: 'root',
  children: [
    { value: '1' },
    { value: '2', children: [{ value: '2.1' }, { value: '2.2' }] }
  ]
});
// Output: Ok<Node<string>>({value: {value: 'root', children: [....]}})

treeDecoder.decode({
  value: 'root',
  children: null
});
// Output: Err({error: "<Node<string>> decoder failed at key 'children' with error: <Node<string>[] | isUndefined> decoder failed because null can't be decoded with any of the provided oneOf decoders"})

JsonDecoder.failover

failover<a>(defaultValue: a, decoder: Decoder<a>): Decoder<a>

Creates a decoder that returns a default value on failure.

@param defaultValue: a

The returned Ok default value when the decoder fails.

@param decoder: Decoder<a>

The decoder that the JSON will be decoded with.

JsonDecoder.failover('default value', JsonDecoder.string).decode(
  'This is fine'
);
// Ok<string>({value: 'This is fine'})

JsonDecoder.failover('default value', JsonDecoder.string).decode(null);
// Ok<string>({value: 'default value'})

JsonDecoder.succeed

succeed: Decoder<any>

Creates a decoder that always succeeds.

JsonDecoder.succeed.decode(null); // Ok<any>({value: null})

JsonDecoder.fail

fail<a>(error: string): Decoder<a>

Creates a decoder that always fails.

@param error: string

The error message that will be returned with the Err instance.

JsonDecoder.fail('Something wrong happened').decode('This is fine');
// Err({error: 'Something wrong happened'})

JsonDecoder.isNull

isNull<a>(defaultValue: a): Decoder<a>

Succeeds when JSON is strictly (===) null and returns a defaultValue.

@param defaultValue: a

The returned default value when JSON is null.

JsonDecoder.isNull('default value').decode(null);
// Ok({value: 'default value'})

JsonDecoder.isNull('default value').decode(999);
// Err({error: '999 is not null'})

JsonDecoder.isUndefined

isUndefined<a>(defaultValue: a): Decoder<a>

Succeeds when JSON is strictly (===) undefined and returns a defaultValue.

@param defaultValue: a

The returned default value when JSON is undefined.

JsonDecoder.isUndefined('default value').decode(undefined);
// Ok({value: 'default value'})

JsonDecoder.isUndefined('default value').decode(999);
// Err({error: '999 is not undefined'})

JsonDecoder.isExactly

isExactly<a>(value: a): Decoder<a>

Succeeds when JSON is strictly (===) value: a and returns value: a.

@param value: a

The value that will be returned when the JSON is strictly equal to it.

JsonDecoder.isExactly(true).decode(true);
// Ok({value: true})

JsonDecoder.isExactly(999).decode(true);
// Err({error: 'true is not 999'})

JsonDecoder.constant

constant<a>(value: a): Decoder<a>

Decoder that always succeeds returning value.

@param value: a

The value that will always be returned.

JsonDecoder.constant(true).decode(false);
// Ok({value: true})