From 5d376ff6a6962b6f536780932676a5e1bc96ade5 Mon Sep 17 00:00:00 2001 From: Elias Mulhall Date: Tue, 20 Feb 2018 12:49:37 -0500 Subject: [PATCH 1/3] Add lodash as explicit dev dependency lodash was already required for a number of other packages, such as babel. Now it's also needed for the `isEqual` function. --- package.json | 1 + yarn.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a0aa0c1..c90a91c 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "colors": "^1.1.2", "cross-env": "^5.0.1", "jest": "^22.0.2", + "lodash": "^4.17.5", "prettier": "^1.4.4", "rimraf": "^2.6.1", "rollup": "^0.53.0", diff --git a/yarn.lock b/yarn.lock index 372be50..d31df42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1917,7 +1917,7 @@ lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" -lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4: +lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.17.5: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" From f70f00c87598b758c1e88b2cb11992697237052a Mon Sep 17 00:00:00 2001 From: Elias Mulhall Date: Tue, 20 Feb 2018 12:32:15 -0500 Subject: [PATCH 2/3] Allow constant decoder to match constant arrays and objects Augment `constant` so that it can be used on any json data. This shouldn't impact performance for preexisting use cases for `constant`, but will perform a deep structural comparison for object and array decoders. Update documentation to show that string and number literal types inside of arrays and objects still require type annotations. --- src/decoder.ts | 25 +++++++++++++++---------- test/json-decode.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/decoder.ts b/src/decoder.ts index 0e1277c..3219f22 100644 --- a/src/decoder.ts +++ b/src/decoder.ts @@ -1,4 +1,5 @@ import * as Result from './result'; +const isEqual = require('lodash/isEqual'); // this syntax avoids TS1192 /** * Information describing how json data failed to match a decoder. @@ -177,15 +178,19 @@ export class Decoder { * and numbers, as detailed by this table: * * ``` - * | Decoder | Type | - * | ---------------------------- | ----------------- | - * | constant(true) | Decoder | - * | constant(false) | Decoder | - * | constant(null) | Decoder | - * | constant('alaska') | Decoder | - * | constant<'alaska'>('alaska') | Decoder<'alaska'> | - * | constant(50) | Decoder | - * | constant<50>(50) | Decoder<50> | + * | Decoder | Type | + * | ---------------------------- | ---------------------| + * | constant(true) | Decoder | + * | constant(false) | Decoder | + * | constant(null) | Decoder | + * | constant('alaska') | Decoder | + * | constant<'alaska'>('alaska') | Decoder<'alaska'> | + * | constant(50) | Decoder | + * | constant<50>(50) | Decoder<50> | + * | constant([1,2,3]) | Decoder | + * | constant<[1,2,3]>([1,2,3]) | Decoder<[1,2,3]> | + * | constant({x: 't'}) | Decoder<{x: string}> | + * | constant<{x: 't'}>({x: 't'}) | Decoder<{x: 't'}> | * ``` * * @@ -234,7 +239,7 @@ export class Decoder { static constant(value: any): Decoder { return new Decoder( (json: any) => - json === value + isEqual(json, value) ? Result.ok(value) : Result.err({message: `expected ${JSON.stringify(value)}, got ${JSON.stringify(json)}`}) ); diff --git a/test/json-decode.test.ts b/test/json-decode.test.ts index 3441acb..2b1f74c 100644 --- a/test/json-decode.test.ts +++ b/test/json-decode.test.ts @@ -146,6 +146,28 @@ describe('constant', () => { expect(decoder.run({x: null})).toEqual({ok: true, result: {x: null}}); }); + + it('can decode a constant array', () => { + type A = [1, 2, 3]; + const decoder: Decoder = constant([1, 2, 3]); + + expect(decoder.run([1, 2, 3])).toEqual({ok: true, result: [1, 2, 3]}); + expect(decoder.run([1, 2, 3, 4])).toMatchObject({ + ok: false, + error: {at: 'input', message: 'expected [1,2,3], got [1,2,3,4]'} + }); + }); + + it('can decode a constant object', () => { + type O = {a: true; b: 12}; + const decoder: Decoder = constant({a: true, b: 12}); + + expect(decoder.run({a: true, b: 12})).toEqual({ok: true, result: {a: true, b: 12}}); + expect(decoder.run({a: true, b: 7})).toMatchObject({ + ok: false, + error: {at: 'input', message: 'expected {"a":true,"b":12}, got {"a":true,"b":7}'} + }); + }); }); describe('object', () => { From a710f8383382f947372b4b19b548383c10016567 Mon Sep 17 00:00:00 2001 From: Elias Mulhall Date: Tue, 20 Feb 2018 12:50:00 -0500 Subject: [PATCH 3/3] Update Docs --- docs/classes/_decoder_.decoder.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/classes/_decoder_.decoder.md b/docs/classes/_decoder_.decoder.md index 55192f5..0b41874 100644 --- a/docs/classes/_decoder_.decoder.md +++ b/docs/classes/_decoder_.decoder.md @@ -386,15 +386,19 @@ Note that `constant('string to match')` returns a `Decoder` which fails Providing the type parameter is only necessary for type-literal strings and numbers, as detailed by this table: - | Decoder | Type | - | ---------------------------- | ----------------- | - | constant(true) | Decoder | - | constant(false) | Decoder | - | constant(null) | Decoder | - | constant('alaska') | Decoder | - | constant<'alaska'>('alaska') | Decoder<'alaska'> | - | constant(50) | Decoder | - | constant<50>(50) | Decoder<50> | + | Decoder | Type | + | ---------------------------- | ---------------------| + | constant(true) | Decoder | + | constant(false) | Decoder | + | constant(null) | Decoder | + | constant('alaska') | Decoder | + | constant<'alaska'>('alaska') | Decoder<'alaska'> | + | constant(50) | Decoder | + | constant<50>(50) | Decoder<50> | + | constant([1,2,3]) | Decoder | + | constant<[1,2,3]>([1,2,3]) | Decoder<[1,2,3]> | + | constant({x: 't'}) | Decoder<{x: string}> | + | constant<{x: 't'}>({x: 't'}) | Decoder<{x: 't'}> | One place where this happens is when a type-literal is in an interface: