🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

@schroffl/json-mapping

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@schroffl/json-mapping - npm Package Compare versions

Comparing version

to
2.0.0

bench.js

581

index.d.ts

@@ -0,35 +1,608 @@

/**
* A library for constructing and running decoders that make sure your data
* looks as expected.
*
* This is a tool that is useful for bringing values safely into your code.
* The source of the data might be something like an HTTP API, user input or
* localStorage, where you can never be sure that you get what you expect.
*
* @packageDocumentation
*/
/**
* This is intended to be an opaque type.
* You really shouldn't be building `Decoder<T>` values on your own (because
* you can't, but TypeScript doesn't stop you). Use the exposed functions
* for that.
*
* If you ever feel like something is missing, please create an issue in the
* GitHub repository at https://github.com/schroffl/json-mapping
*
* @typeParam T - When running this decoder you get a value of this type
*/
export type Decoder<T> = {
/**
* @internal
*/
readonly __opaque_type: 'decoder'
/**
* @internal
*/
readonly __type: T
}
/**
* Mapped type that creates an object from the given type where every property
* is optional, but wraps the actual type in a Decoder. It's used by
* {@link Decode.object} and {@link Decode.instance}.
*
* @typeParam O - The type to generate the layout for
*
* @example
* If you had a User model like
* ```typescript
* class User {
* id: number;
* name: string;
* children: User[];
* }
* ```
*
* the mapped layout would look like this:
*
* ```typescript
* type UserLayout = {
* id?: Decoder<number>,
* name?: Decoder<string>,
* children?: Decoder<User[]>,
* }
* ```
*/
export type ObjectLayout<O> = {
[K in keyof O]?: Decoder<O[K]>
}
/**
* This namespace wraps all the decoders exposed by this package.
* It contains primitive decoders like {@link Decode.string} to more
* complicated ones like {@link Decode.map}.
* So basically all the building blocks you need for creating decoders for
* complex data structures.
*/
export namespace Decode {
/**
* Decode any valid JavaScript Number that is not NaN
*
* @example
* ```typescript
* decode(Decode.number, 42.2) == 42.2
* decode(Decode.number, 42) == 42
* decode(Decode.number, NaN) // Throws
* ```
*/
export const number: Decoder<number>
/**
* Decode any string value.
*
* @example
* ```typescript
* decode(Decode.string, 'abc') === 'abc'
* decode(Decode.string, 'my-string') === 'my-string'
* decode(Decode.string, 10) // Throws
* ```
*/
export const string: Decoder<string>
/**
* Decode an integer. Floating point values are not accepted.
*
* @example
* ```typescript
* decode(Decode.number, 42) == 42
* decode(Decode.number, 42.2) // Throws
* decode(Decode.number, NaN) // Throws
* ```
*/
export const integer: Decoder<number>
/**
* Decode either `true` or `false`. Nothing else.
*
* @example
* ```typescript
* decode(Decode.bool, true) === true
* decode(Decode.bool, false) === false
* decode(Decode.bool, undefined) // Throws
* ```
*/
export const bool: Decoder<boolean>
/**
* Decode the value as-is. You probably shouldn't use this, because there's
* a high chance you're abusing it as an escape-hatch.
* However, it has a valid use case for building custom decoders with the
* help of {@link Decode.andThen}. If you do that, please make sure that you keep
* everything safe.
*
* @example
* ```typescript
* decode(Decode.unknown, true) === true
* decode(Decode.unknown, undefined) === undefined
* decode(Decode.unknown, NaN)
*
* // This decoder really blindly passes on the value
* const symbol = Symbol('my-symbol');
* decode(Decode.unknown, symbol) === symbol
* ```
*/
export const unknown: Decoder<unknown>
export type ObjectLayout<O> = {
[K in keyof O]?: Decoder<O[K]>
}
/**
* Decode the value to an object. This implies nothing about the value that
* is being decoded, which can be anything.
* You could take a "plain" integer and "lift" it into an object.
*
* @example
* ```typescript
* const decoder = Decode.object({
* value: Decode.integer,
* });
*
* decode(decoder, 42) // { value: 42 }
* decode(decoder, 100) // { value: 100 }
* decode(decoder, '100') // Fails
* ```
*
* @param layout - Properties you want the object to have
*/
export function object<O>(layout: ObjectLayout<O>) : Decoder<O>
export function object<O>(layout: ObjectLayout<O>) : Decoder<O>
/**
* Decode an object with arbitrary keys and values of the same type T.
*
* @param child - Decoder to use for values
*
* @example
* ```typescript
* const raw = { en: 'Bread', fr: 'Pain', it: 'Pane' };
* const decoder = Decode.dict(Decode.string);
*
* decode(decoder, raw); // Works
*
* decode(decoder, { en: 128 }); // Fails
* ```
*/
export function dict<T>(child: Decoder<T>) : Decoder<{ [key: string]: T }>
/**
* This is mostly equivalent to {@link Decode.object}, but it creates an
* instance of the given class first. You should only use this for simple
* classes that don't have a complex constructor. Use `map` or `andThen`
* for complicated cases.
*
* @example
* If you had the following User model in your application
*
* ```typescript
* class User {
* id!: number;
* name!: string;
*
* get initials(): string {
* const parts = this.name.split(' ');
* return parts[0][0] + parts[1][0];
* }
* }
* ```
*
* your instance decoder would look like this.
*
* ```typescript
* const UserDecoder = Decode.instance(User, {
* id: Decode.field('id', Decode.integer),
* name: Decode.field('name', Decode.string),
* });
* ```
*
* Running this decoder on raw values will ensure that you
* always have an actual instance of your User class at hand.
*
* ```typescript
* const kobe = decode(UserDecoder, { id: 3, name: 'Kobe Bryant' });
* console.assert(kobe.id === 3)
* console.assert(kobe.name === 'Kobe Bryant')
* console.assert(kobe.initials === 'KB')
* ```
*
* @param ctor - The class you want to construct an instance of
* @param layout - Properties you want to set on the instance
*
* @see object
* @see ObjectLayout
*/
export function instance<O>(ctor: new () => O, layout: ObjectLayout<O>) : Decoder<O>
/**
* Access the given property of an object.
*
* @param name - Name of the property
* @param child - The decoder to run on the value of that property
*
* @example
* ```typescript
* const decoder = Decode.field('value', Decode.integer);
*
* decode(decoder, { value: 42 }) === 42
* decode(decoder, {}) // Fails
* decode(decoder, 42) // Fails
* ```
*/
export function field<T>(name: string, child: Decoder<T>) : Decoder<T>
/**
* It's basically the same as {@link Decode.field}, but makes it easier
* to define deep property paths.
* Instead of `Decode.field('outer', Decode.field('inner', Decode.string))`
* you can use `Decode.at(['outer', 'inner'], Decode.string)`
*
* @param path - The property path to follow. The first name is the
* outer-most field
* @param child - The decoder to run on that field
*
* @example
* When you want to access the `name` field in an object like this
* ```json
* {
* "data": {
* "outer": {
* "inner": {
* "name": "Kobe Bryant",
* },
* },
* },
* }
* ```
*
* you would have to chain quite a few {@link Decode.field | field}
* decoders, which is annoying.
*
* ```typescript
* const decoder = Decode.field(
* 'data',
* Decode.field(
* 'outer',
* Decode.field(
* 'inner',
* Decode.field(
* 'name',
* Decode.string,
* ),
* ),
* ),
* );
*
* decode(decoder, raw) === 'Kobe Bryant'
* ```
*
* {@link Decode.at} allows us to be more concise.
*
* ```typescript
* const short = Decode.at(['data', 'outer', 'inner', 'name'], Decode.string);
* decode(short, raw) === 'Kobe Bryant'
* ```
*/
export function at<T>(path: string[], child: Decoder<T>) : Decoder<T>
/**
* Make a decoder that can be used for decoding arrays, where
* every value is run through the given child decoder.
*
* @param child - Decoder for array items
*
* @example
* Suppose we have a decoder for Users
* ```typescript
* class User {}
*
* const user_decoder = Decode.instance(User, {
* id: Decode.field('id', Decode.integer),
* name: Decode.field('name', Decode.string),
* });
* ```
*
* Using {@link Decode.many} we can easily build a decoder for a list of users:
*
* ```typescript
* const decoder = Decode.many(user_decoder);
*
* decode(decoder, [ {id: 1, name: 'Jeff'}, {id: 2, name: 'Jake'} ]);
* ```
*
* @returns Decoder for an array of things
*/
export function many<T>(child: Decoder<T>) : Decoder<T[]>
/**
* Take the value decoded by the given decoder and do something
* with it.
*
* @param fn - Your mapping function
* @param child - The decoder to run before calling your function
*
* @typeParam A - Input to the mapping function
* @typeParam B - The mapping function returns a value of this type
*
* @example
* ```typescript
* // TODO
* ```
*/
export function map<A, B>(fn: (a: A) => B, child: Decoder<A>) : Decoder<B>
/**
* Similar to {@link Decode.map}, but you return a decoder instead of a
* value.
* This allows you to decide how to continue decoding depending on the
* result.
*
* @param fn - Mapping function that returns a decoder
* @param child - The decoder to run before the mapping function
*
* @typeParam A - Input to the mapping function
* @typeParam B - The mapping function returns a decoder for this type
*
* @example
* Maybe our HTTP API of choice wraps the data in an object that contains
* information about the success of the operation.
* Depending on that value we want to handle the data differently.
*
* ```typescript
* const UserDecoder = Decode.object({
* id: Decode.field('id', Decode.integer),
* name: Decode.field('name', Decode.string),
* });
*
* const response_decoder = Decode.andThen(
* success => {
* if (success) {
* return Decode.field('data', UserDecoder);
* } else {
* return Decode.andThen(
* error => Decode.fail('Got an error response: ' + error),
* Decode.field('error', Decode.string),
* );
* }
* },
* Decode.field('success', Decode.bool),
* );
*
* // Works
* decode(response_decoder, {
* success: true,
* data: {
* id: 1,
* name: 'Kobe Bryant',
* },
* });
*
* // Fails
* decode(response_decoder, {
* success: false,
* error: 'Could not find user!',
* });
* ```
*
* This is nice and all, but it only works for the UserDecoder.
* However, making it generic is rather simple. You just wrap the Decoder
* in a function and accept the data decoder as an argument:
*
* ```typescript
* function responseDecoder<T>(child: Decoder<T>): Decoder<T> {
* return Decode.andThen(success => {
* if (success) {
* return Decode.field('data', child);
* } else {
* // etc.
* }
* }, Decode.field('success', Decode.boolean');
* }
* ```
*/
export function andThen<A, B>(fn: (a: A) => Decoder<B>, child: Decoder<A>) : Decoder<B>
/**
* Combine multiple decoders where any of them can match for the resulting
* decoder to succeed.
*
* @param decoders - A list of decoders that will be executed
*
* @example
* For this example we assume that we have an API endpoint that returns
* either a numeric string or a number. Something like this:
*
* ```json
* [
* { id: 1 },
* { id: '2' },
* ]
* ```
*
* which can be decoded to a flat numeric array like this:
*
* ```typescript
* const string_to_number_decoder = Decode.andThen(str => {
* const v = parseInt(str, 10);
*
* if (typeof v === 'number' && !isNaN(v)) {
* return Decode.succeed(v);
* } else {
* return Decode.fail(expected('a number string', str));
* }
* }, Decode.string);
*
* const decoder = Decode.oneOf([
* Decode.number,
* string_to_number_decoder,
* ]);
*
* // This gives us an array like [1, 2], where both values are actual
* // numbers
* decode(Decode.many(Decode.field('id', decoder)), [
* { id: 1 },
* { id: '2' },
* ]);
* ```
*/
export function oneOf<T>(decoders: Decoder<T>[]) : Decoder<T>
/**
* If the decoder fails, the given value is returned instead.
*
* @example
* ```typescript
* const decoder = Decode.optional(42, Decode.number);
*
* decode(decoder, 100) === 100
* decode(decoder, -10) === -10
*
* decode(decoder, '3') === 42
* decode(decoder, NaN) === 42
* decode(decoder, null) === 42
* ```
*
* @remarks This is implemented by using {@link Decode.oneOf}:
* ```typescript
* function optional(value, child) {
* return Decode.oneOf([ child, Decode.succeed(value) ]);
* }
* ```
*/
export function optional<T>(value: T, child: Decoder<T>) : Decoder<T>
/**
* Make decoder that runs the given function and uses its result for
* decoding.
* This can be used for nested decoding, where you would otherwise get a
* `Cannot access 'value' before initialization` error.
*
* @param fn - Function that returns the actual decoder
*
* @example
* ```typescript
* class Tree {}
*
* const tree_decoder = Decode.instance(Tree, {
* value: Decode.field('value', Decode.integer),
* children: Decode.field(
* 'children',
* Decode.lazy(() => Decode.many(tree_decoder)),
* ),
* });
*
* const raw = {
* value: 42,
* children: [
* { value: 43, children: [] },
* {
* value: 44,
* children: [
* { value: 45, children: [] },
* ],
* },
* ]
* };
*
* decode(tree_decoder, raw) // Decodes the nested tree structure
* ```
*
* @returns A decoder that calls the given function when its executed. The
* returned value is what will then be used for decoding.
*/
export function lazy<T>(fn: () => Decoder<T>) : Decoder<T>
/**
* Make a decoder that *always* succeeds with the given value.
* The input is basically ignored.
*
* @param value - The returned value
*
* @example
* ```typescript
* const decoder = Decode.succeed(42);
*
* decode(decoder, 'string') === 42
* decode(decoder, {}) === 42
* decode(decoder, null) === 42
* decode(decoder, 42) === 42
* ```
*
* @returns A decoder that always succeeds with the given value when executed
*/
export function succeed<T>(value: T) : Decoder<T>
/**
* Make a decoder that *never* succeeds and fails with the given message.
* This is mostly useful for building custom decoders with `andThen`.
*
* @param message - Custom error message
*
* @example
* This example uses {@link expected} to create the error message.
*
* ```typescript
* const base64_decoder = Decode.andThen(str => {
* try {
* return Decode.succeed(atob(str));
* } catch {
* return Decode.fail(expected('a valid base64 string', str));
* }
* }, Decode.string);
*
* decode(base64_decoder, 'SGVsbG8sIFdvcmxkIQ==') === 'Hello, World!'
* decode(base64_decoder, 'invalid$') // Throws
* ```
*
* @returns A decoder that always fails when executed
*/
export function fail<T>(message: string) : Decoder<T>
}
/**
* Run the given decoder on the given input.
*
* @param decoder - The decoder to use
* @param json - The unknown value you want to decode
*
* @typeParam T - The result type of the decoder and also return type
* of the function
*
* @throws If any decoder causes an error this function will throw it
* @returns The value as advertised by the decoder
*/
export function decode<T>(decoder: Decoder<T>, json: any) : T
/**
* This is the same as {@link decode}, but it accepts a JSON string instead of
* a JavaScript value.
*
* @param decoder - The decoder to use
* @param json - The JSON string
*
* @see decode
*/
export function decodeString<T>(decoder: Decoder<T>, json: string) : T
/**
* Useful for building error messages for your own little decoders.
* Since this function is used internally, the message layout will be the same,
* which makes it easier for humans to parse error messages. Especially when
* decoding complex values.
*
* @param description - Describe what kind of value you expected
* @param value - Whatever value you got instead
*
* @returns A nicely formatted error string
*/
export function expected(description: string, value: any) : string
// The export pattern is a UMD template:
// https://github.com/umdjs/umd/blob/1deb860078252f31ced62fa8e7694f8bbfa6d889/templates/returnExports.js
(function (root, factory) {
/* c8 ignore start */
if (typeof define === 'function' && define.amd) {

@@ -16,2 +17,3 @@ // AMD. Register as an anonymous module.

}
/* c8 ignore stop */
}(this, function () {

@@ -40,2 +42,4 @@ var Decode = {};

var DICT = 16;
var FIELD_ERROR_META = 999;

@@ -67,2 +71,3 @@

/* c8 ignore start */
function debugReplace(key, value) {

@@ -100,3 +105,3 @@ if (key === '') {

} else {
return '<Object with ' + fieldCount + 'fields, like ' + fieldStr + '>';
return '<Object with ' + fieldCount + ' fields, like ' + fieldStr + '>';
}

@@ -107,2 +112,3 @@ } else {

}
/* c8 ignore stop */

@@ -131,3 +137,3 @@ function toDebugString(value) {

case NUMBER:
if (typeof value !== 'number') {
if (typeof value !== 'number' || isNaN(value)) {
return err(expected('a number', value));

@@ -139,3 +145,3 @@ } else {

case INTEGER:
if (typeof value !== 'number' || (value | 0) !== value) {
if (typeof value !== 'number' || Math.trunc(value) !== value) {
return err(expected('an integer', value));

@@ -166,4 +172,2 @@ } else {

}
return decodeInternal(decoder.child, value[decoder.key]);
}

@@ -254,2 +258,23 @@ }

}
case DICT: {
if (typeof value !== 'object' || value === null) {
return err(expected('an object', value));
} else {
var result = {};
for (var key in value) {
var child_value = decodeInternal(decoder.child, value[key]);
if (isOk(child_value)) {
result[key] = child_value.value;
} else {
// TODO Wrap in key info
return child_value;
}
}
return ok(result);
}
}
}

@@ -345,2 +370,6 @@ }

Decode.dict = function(child) {
return { tag: DICT, child: child };
};
return {

@@ -353,4 +382,6 @@ Decode: Decode,

return decode(decoder, val);
}
},
expected: expected,
};
}));

28

package.json
{
"name": "@schroffl/json-mapping",
"version": "1.2.1",
"description": "A set of utilites for defining and running conversions between JSON and JavaScript values",
"main": "index.js",
"types": "index.d.ts",
"directories": {},
"scripts": {},
"author": "schroffl",
"license": "MIT",
"devDependencies": {
"benchmark": "^2.1.4"
}
"name": "@schroffl/json-mapping",
"version": "2.0.0",
"description": "A set of utilites for defining and running conversions between JSON and JavaScript values",
"main": "index.js",
"types": "index.d.ts",
"directories": {},
"scripts": {
"test": "c8 -r html ava"
},
"author": "schroffl",
"license": "MIT",
"devDependencies": {
"ava": "^4.0.1",
"benchmark": "^2.1.4",
"c8": "^7.11.3"
}
}
# json-mapping
A set of utilites for defining and running conversions between JSON and JavaScript values.
Heavily inspired by [`elm/json`](https://github.com/elm/json).

@@ -1,15 +0,328 @@

"use strict";
exports.__esModule = true;
var index_1 = require("./index");
var internal = index_1.Decode.object({
y: index_1.Decode.object({
z: index_1.Decode.field('x', index_1.Decode.unknown)
})
const test = require('ava');
const { Decode, decode, expected, decodeString } = require('./index');
const make = decoder => decode.bind(undefined, decoder);
test('Decode.string', t => {
const run = make(Decode.string);
t.is(run('string'), 'string');
t.throws(() => run(42));
});
var decoder = index_1.Decode.andThen(function (value) {
return index_1.Decode.succeed(value);
}, internal);
var result = index_1.decode(decoder, {
x: 42
test('Decode.number', t => {
const run = make(Decode.number);
t.is(run(42), 42);
t.is(run(42.42), 42.42);
t.throws(() => run('42'));
t.throws(() => run(NaN));
});
console.log(result);
test('Decode.integer', t => {
const run = make(Decode.integer);
t.is(run(42), 42);
t.is(run(Number.MAX_SAFE_INTEGER), Number.MAX_SAFE_INTEGER);
t.is(run(Number.MIN_SAFE_INTEGER), Number.MIN_SAFE_INTEGER);
t.throws(() => run(42.42));
t.throws(() => run('42'));
t.throws(() => run(NaN));
});
test('Decode.bool', t => {
const run = make(Decode.bool);
t.is(run(false), false);
t.is(run(true), true);
t.throws(() => run(0));
t.throws(() => run(1));
t.throws(() => run('true'));
t.throws(() => run('false'));
t.throws(() => run(null));
t.throws(() => run(undefined));
t.throws(() => run(''));
});
test('Decode.unknown', t => {
const run = make(Decode.unknown);
const ref = {};
const sym = Symbol();
t.is(run('abc'), 'abc');
t.is(run(false), false);
t.is(run(Number.MAX_VALUE), Number.MAX_VALUE);
t.is(run(ref), ref);
t.is(run(sym), sym);
});
test('Decode.succeed', t => {
const run = make(Decode.succeed(42));
t.is(run(undefined), 42);
t.is(run(null), 42);
t.is(run('ababababa'), 42);
t.is(run({}), 42);
t.is(run(Symbol()), 42);
t.is(run(42.2), 42);
t.is(run(42), 42);
});
test('Decode.fail', t => {
const run = make(Decode.fail('fail'));
t.throws(() => run(undefined));
t.throws(() => run(null));
t.throws(() => run('ababababa'));
t.throws(() => run({}));
t.throws(() => run(Symbol()));
t.throws(() => run(42.2));
t.throws(() => run(42));
});
test('Decode.field', t => {
const run = make(Decode.field('value', Decode.integer));
t.is(run({ value: 42 }), 42);
t.is(run({ value: -42 }), -42);
t.throws(() => run(null));
t.throws(() => run({ }));
t.throws(() => run({ value: '42' }));
t.throws(() => run({ valu: 42 }));
t.throws(() => run({ value: { value: 42 } }));
});
test('Decode.at', t => {
const run = make(Decode.at(['a', 'b', 'c', 'd'], Decode.integer));
t.is(run({a: {b: {c: {d: 42 }}}}), 42);
t.throws(() => run({a: {b: {c: {e: 42 }}}}));
t.throws(() => run({a: {b: {c: {d: '42' }}}}));
t.throws(() => run({a: {d: '42' }}));
});
test('Decode.many', t => {
const run = make(Decode.many(Decode.integer));
t.deepEqual(run([]), []);
t.deepEqual(run([1, 2, 3]), [1, 2, 3]);
t.throws(() => run(['1', 2, 3]));
t.throws(() => run({}));
t.throws(() => run(null));
t.throws(() => run(undefined));
});
test('Decode.oneOf', t => {
const string_to_int_decoder = Decode.andThen(str => {
const v = parseInt(str, 10);
if (typeof v === 'number' && !isNaN(v) && Math.trunc(v) === v) {
return Decode.succeed(v);
} else {
return Decode.fail(expected('a number string', str));
}
}, Decode.string);
const decoder = Decode.oneOf([
Decode.integer,
string_to_int_decoder,
]);
const result = decode(Decode.many(Decode.field('id', decoder)), [
{ id: 1 },
{ id: '2' },
]);
t.deepEqual(result, [1, 2]);
const failing = Decode.oneOf([
Decode.integer,
Decode.string,
]);
t.throws(() => decode(failing, true));
});
test('Decode.andThen', t => {
const run = make(
Decode.andThen(
v => v > 100 ? Decode.succeed('big') : Dec.fail('too small'),
Decode.integer,
),
);
t.is(run(101), 'big');
t.throws(() => run(42));
t.throws(() => run(100));
t.throws(() => run('42'));
});
test('Decode.dict', t => {
const run = make(Decode.dict(Decode.integer));
const arg = { a: 42, b: 102 };
const result = run({ a: 42, b: 102 });
t.not(result, arg); // They should *not* be the same object
t.deepEqual(result, arg);
t.throws(() => run({ a: 42, b: 102, c: '3' }));
t.throws(() => run({ a: 42, b: 102, c: 42.2 }));
t.throws(() => run('abc'));
t.throws(() => run(null));
});
test('Decode.object', t => {
const run = make(Decode.object({ value: Decode.integer }));
t.deepEqual(run(42), { value: 42 });
// Nested object
const run_other = make(
Decode.object({
name: Decode.field('name', Decode.string),
position: Decode.object({
latitude: Decode.field('lat', Decode.number),
longitude: Decode.field('lng', Decode.number),
}),
}),
);
t.deepEqual(
run_other({
name: 'Taj Mahal',
lat: 27.175000,
lng: 78.041944,
}),
{
name: 'Taj Mahal',
position: {
latitude: 27.175000,
longitude: 78.041944,
},
},
);
t.throws(() => {
run_other({
name: 'abc',
lat: '27',
lng: '78',
});
});
});
test('Decode.instance', t => {
class User {
initials() {
const parts = this.name.split(' ');
return parts[0][0] + parts[1][0];
}
}
const UserDecoder = Decode.instance(User, {
id: Decode.field('id', Decode.integer),
name: Decode.field('name', Decode.string),
});
const user = decode(UserDecoder, { id: 42, name: 'Yo Bo' });
t.true(user instanceof User);
t.is(user.initials(), 'YB');
t.throws(() => decode(UserDecoder, {}));
});
test('Decode.lazy', t => {
const Tree = class {};
const Tree_decoder = Decode.instance(Tree, {
value: Decode.field('value', Decode.integer),
nodes: Decode.field(
'nodes',
Decode.lazy(() => Decode.many(Tree_decoder)),
),
});
const raw = {
value: 42,
nodes: [
{ value: 103, nodes: [] },
{
value: 104,
nodes: [],
},
]
};
const tree = decode(Tree_decoder, raw);
t.is(tree.value, 42);
t.is(tree.nodes[0].value, 103);
t.is(tree.nodes[1].value, 104);
t.true(tree instanceof Tree);
t.true(tree.nodes[0] instanceof Tree);
t.true(tree.nodes[1] instanceof Tree);
});
test('Decode.optional', t => {
const run = make(Decode.optional(42, Decode.integer));
t.is(run(100), 100);
t.is(run(-10), -10);
t.is(run(NaN), 42);
t.is(run('3'), 42);
t.is(run(null), 42);
});
test('Decode.map', t => {
const run = make(Decode.map(n => n * 10, Decode.integer));
t.is(run(1), 10);
t.is(run(10), 100);
t.is(run(0), 0);
t.throws(() => run('10'));
t.throws(() => run('0'));
});
test('expected', t => {
const msg = expected('some type');
t.true(typeof msg === 'string');
});
// `decodeString` should produce the same result as `decode`.
test('decodeString', t => {
const cases = [
{ value: true, decoder: Decode.bool },
{ value: 100, decoder: Decode.integer },
{ value: 100.1, decoder: Decode.integer },
{ value: 100.1, decoder: Decode.number },
{ value: { a: 42 }, decoder: Decode.field('a', Decode.integer) },
];
cases.map(caze => {
const str = JSON.stringify(caze.value);
return {
normal: () => decode(caze.decoder, caze.value),
stringified: () => decodeString(caze.decoder, str),
};
}).forEach(caze => {
let result;
try {
result = caze.normal();
} catch (e) {
t.throws(caze.stringified);
return;
}
t.deepEqual(caze.stringified(), result);
});
});

Sorry, the diff of this file is not supported yet