Decoder.js
Turn arbitrary JavaScript values into valid TypeScript values. Inspired by Elm's JSON Decoder library.
This library is great at the edges of your program when you want to validate the structure of data not created by your program (for example: HTTP responses, user input, functions that return any
).
To find an example of this library used for an HTTP response, check the example
directory. It is OK to to transform the input into a data structure that makes more sense for your application. For example, the data returned by the CDC in the example
directory is an array with the values in different indexes. You can transform that array of values into a structured object. It also returns number as strings, which you can change as well.
;;;// successfulResult = success(1);// failedResult = failure(nonEmptyArray.of('Value must be a number, found "string" instead'))
Note: This library uses Giulio Canti's functional library to provide different data structures like Option
and NonEmptyArray
. Let me know if you need any help with this library.
Primitives
Type Decoder
A value that knows how to turn unknown inputs into typed outputs.
() => Decoder<boolean>
Boolean: Succeeds with a boolean value.
;Decoder.boolean.runtrue;// Success: trueDecoder.boolean.runfalse;// Success: falseDecoder.boolean.run10;// FailureDecoder.boolean.run'string';// Failure
() => Decoder<number>
Number: Succeeds with a number value.
;Decoder.number.run10;// Success: 10Decoder.number.runfalse;// FailureDecoder.number.run'string';// Failure
() => Decoder<string>
String: Succeeds with a string value.
;Decoder.string.run'string';// Success: 'string'Decoder.string.runfalse;// FailureDecoder.string.run10;// Failure
Data Structures
(Decoder<a>) => Decoder<a[]>
Array: Succeeds with an array of values.
;Decoder.arrayDecoder.boolean.run;// Success: [true, false, true]Decoder.arrayDecoder.number.run;// Success: [10, 11, 12]
(Decoder<a>) => Decoder<NonEmptyArray<a>>
Non-Empty Array: Succeeds with a non-empty array of values. This decoders guarantees at least one value in the array.
;Decoder.nonEmptyArrayDecoder.boolean.run;// Success: NonEmptyArray(true, [false, true])Decoder.nonEmptyArrayDecoder.boolean.run;// Failure
(Decoder<a>) => Decoder<Option<a>>
Nullable: Succeeds with null or a value.
;Decoder.nullableDecoder.boolean.runnull;// Success: NoneDecoder.nullableDecoder.boolean.runtrue;// Success: Some(true)Decoder.nullableDecoder.boolean.run'string';// Failure
Object Primitives
(string, Decoder<a>) => Decoder<a>
Field: Succeeds when an object contains a certain field.
;Decoder.field'isAdult', Decoder.boolean.run;// Success: trueDecoder.field'isAdult', Decoder.boolean.run;// Success: trueDecoder.field'isAdult', Decoder.boolean.run;// FailureDecoder.field'isAdult', Decoder.boolean.run;// Failure
The object can have other fields. Lots of them! The only thing this decoder cares about is if isAdult
is present and that the value there is a boolean.
Check out map2
to see how to decode multiple fields!
(string, Decoder<a>) => Decoder<Option<a>>
Optional Field: Similar to field
, but succeeds whether the value is present or not.
;Decoder.optionalField'isAdult', Decoder.boolean.run;// Success: Some(true)Decoder.optionalField'isAdult', Decoder.boolean.run;// Success: Some(true)Decoder.optionalField'isAdult', Decoder.boolean.run;// FailureDecoder.optionalField'isAdult', Decoder.boolean.run;// Success: None
(string[], Decoder<a>) => Decoder<a>
At: Succeeds when an object contains nested fields.
;;Decoder.at, Decoder.boolean.runperson;// Success: trueDecoder.at, Decoder.number.runperson;// Success: 161.8
This is really just a shorthand for saying things like:
Decoder.field'info', Decoder.field'isAdult', Decoder.boolean.runperson;
(number, Decoder<a>) => Decoder<a>
Index: Succeeds when an array contains a certain index.
;;Decoder.index0, Decoder.string.runnames;// Success: 'Alice'Decoder.index1, Decoder.string.runnames;// Success: 'Bob'Decoder.index2, Decoder.string.runnames;// Success: 'Chuck'Decoder.index3, Decoder.string.runnames;// Failure
Inconsistent Structure
(NonEmptyArray<Decoder<a>>) => Decoder<a>
One Of: Try a bunch of different decoders. This can be useful if the input may come in a couple different formats. For example, say you want to read an array of numbers, but some of them are null.
;;;;Decoder.listbadNumberDecoder.runbadNumbers;// Success: [1, 2, 0, 4]
Why would someone generate data like this? Questions like this are not good for your health. The point is that you can use oneOf
to handle situations like this!
You could also use oneOf
to help version your data. Try the latest format, then a few older ones that you still support. You could use andThen
to be even more particular if you wanted.
Mapping
((a) => b, Decoder<a>) => Decoder<b>
Map: Transform a decoder. Maybe you just want to know an unsigned number:
;;unsignedNumberDecoder.run-1;// Success: 1
It is often helpful to use map with oneOf, like when defining nullable
:
;;
((a, b) => c, Decoder<a>, Decoder<b>) => Decoder<c>
Map2: Try two decoders and then combine the result. We can use this to decode objects with many fields:
;;pointDecoder.run;// Success: IPoint { x: 2, y: 4 }
It tries each individual decoder and puts the result together with the point
function.
((a, b, c) => d, Decoder<a>, Decoder<b>, Decoder<c>) => Decoder<d>
Map3: Try three decoders and then combine the result. We can use this to decode objects with many fields:
;;personDecoder.run;// Success: IPerson { name: 'Tester', height: 1.8, isAdult: true }
Like map2
, it tries each decoder and then give the results to the person
function. That can be any function though!
Decoder<a> => Decoder<a => b> => Decoder<b>
AndMap: When you want to try more than three decoders, you can use andMap
to partially decode a large function of arbitrary length.
;;;;userDecoder.run;// Success: IUser { id: 0, username: 'username', email: 'email', isAdmin: true }
Note: andMap
is meant to be used with succeed
and fp-ts
's curry
and pipe
functions.
Fancy Decoding
() => Decoder<unknown>
Unknown: Do not do anything with the input. This can be useful if you have particularly complex data that you would like to deal with later, or if you are going to send it out and do not care about its structure.
(a) => Decoder<a>
Null: Decode a null
value into some other value.
;Decoder.nullfalse.runnull;// Success: falseDecoder.null42.runnull;// Success: 42Decoder.null42.run42;// FailureDecoder.null42.runfalse;// Failure
So if you ever see a null
, this will return whatever value you specified.
(a) => Decoder<a>
Succeed: Ignore the input and produce a certain output.
;Decoder.succeed42.runtrue;// Success: 42Decoder.succeed42.run;// Success: 42Decoder.succeed42.run'hello';// Success: 42Decoder.succeed42.run;// Success: 42
This is handy when used with oneOf
or andThen
.
(string) => Decoder<a>
Fail: Ignore the input and make the decoder fail. This is handy when used with oneOf
or andThen
where you want to give a custom error message in some case.
See the andThen
docs for an example.
((a) => Decoder<b>, Decoder<a>) => Decoder<b>
And Then: Create decoders that depend on previous results. If you are creating a Direction decoder, you might do something like this:
;Decoder.andThendirectionDecoder, Decoder.string.run'Up';// Succeed: Direction.Up