From 38f2adc433dd87e87bf8d3c953dfe5f58a0415a7 Mon Sep 17 00:00:00 2001 From: Chris Wilkinson Date: Thu, 31 Mar 2022 14:21:09 +0100 Subject: [PATCH] Add json --- docs/modules/Codec.ts.md | 11 +++++++++++ docs/modules/Decoder.ts.md | 11 +++++++++++ docs/modules/Encoder.ts.md | 11 +++++++++++ package-lock.json | 11 ++++++++--- package.json | 8 +++++--- src/Codec.ts | 7 +++++++ src/Decoder.ts | 21 ++++++++++++++++++++- src/Encoder.ts | 10 ++++++++++ test/Codec.ts | 27 +++++++++++++++++++++++++++ test/Decoder.ts | 13 +++++++++++++ test/Encoder.ts | 10 ++++++++++ tslint.json | 2 +- 12 files changed, 134 insertions(+), 8 deletions(-) diff --git a/docs/modules/Codec.ts.md b/docs/modules/Codec.ts.md index 0f5feca24..069f097c5 100644 --- a/docs/modules/Codec.ts.md +++ b/docs/modules/Codec.ts.md @@ -31,6 +31,7 @@ Added in v2.2.3 - [fromSum](#fromsum) - [fromTuple](#fromtuple) - [intersect](#intersect) + - [json](#json) - [lazy](#lazy) - [mapLeftWithInput](#mapleftwithinput) - [nullable](#nullable) @@ -194,6 +195,16 @@ export declare const intersect: ( Added in v2.2.3 +## json + +**Signature** + +```ts +export declare const json: Codec +``` + +Added in v2.2.17 + ## lazy **Signature** diff --git a/docs/modules/Decoder.ts.md b/docs/modules/Decoder.ts.md index 347edd76c..00c946521 100644 --- a/docs/modules/Decoder.ts.md +++ b/docs/modules/Decoder.ts.md @@ -41,6 +41,7 @@ Added in v2.2.7 - [fromSum](#fromsum) - [fromTuple](#fromtuple) - [intersect](#intersect) + - [json](#json) - [lazy](#lazy) - [mapLeftWithInput](#mapleftwithinput) - [nullable](#nullable) @@ -275,6 +276,16 @@ export declare const intersect: ( Added in v2.2.7 +## json + +**Signature** + +```ts +export declare const json: Decoder +``` + +Added in v2.2.17 + ## lazy **Signature** diff --git a/docs/modules/Encoder.ts.md b/docs/modules/Encoder.ts.md index 153decb69..c2eb41758 100644 --- a/docs/modules/Encoder.ts.md +++ b/docs/modules/Encoder.ts.md @@ -28,6 +28,7 @@ Added in v2.2.3 - [combinators](#combinators) - [array](#array) - [intersect](#intersect) + - [json](#json) - [lazy](#lazy) - [nullable](#nullable) - [partial](#partial) @@ -108,6 +109,16 @@ export declare const intersect: (right: Encoder) => (left: Enc Added in v2.2.3 +## json + +**Signature** + +```ts +export declare const json: Encoder +``` + +Added in v2.2.17 + ## lazy **Signature** diff --git a/package-lock.json b/package-lock.json index 1136755a7..e4f3ccd5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3281,9 +3281,9 @@ "dev": true }, "fp-ts": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.5.0.tgz", - "integrity": "sha512-xkC9ZKl/i2cU+8FAsdyLcTvPRXphp42FcK5WmZpB47VXb4gggC3DHlVDKNLdbC+U8zz6yp1b0bj0mZg0axmZYQ==", + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.10.5.tgz", + "integrity": "sha512-X2KfTIV0cxIk3d7/2Pvp/pxL/xr2MV1WooyEzKtTWYSc1+52VF4YzjBTXqeOlSiZsPCxIBpDGfT9Dyo7WEY0DQ==", "dev": true }, "fragment-cache": { @@ -6902,6 +6902,11 @@ "ret": "~0.1.10" } }, + "safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", diff --git a/package.json b/package.json index 59152d8bf..5537c8794 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,11 @@ "url": "https://github.com/gcanti/io-ts/issues" }, "homepage": "https://github.com/gcanti/io-ts", - "dependencies": {}, + "dependencies": { + "safe-stable-stringify": "^2.3.1" + }, "peerDependencies": { - "fp-ts": "^2.5.0" + "fp-ts": "^2.10.0" }, "devDependencies": { "@types/benchmark": "1.0.31", @@ -54,7 +56,7 @@ "dtslint": "github:gcanti/dtslint", "eslint": "^7.18.0", "fast-check": "^1.24.2", - "fp-ts": "^2.5.0", + "fp-ts": "^2.10.5", "import-path-rewrite": "github:gcanti/import-path-rewrite", "jest": "25.2.7", "mocha": "7.1.1", diff --git a/src/Codec.ts b/src/Codec.ts index fe43c2aa5..9412e98aa 100644 --- a/src/Codec.ts +++ b/src/Codec.ts @@ -8,6 +8,7 @@ * * @since 2.2.3 */ +import { Json } from 'fp-ts/lib/Json' import { identity, Refinement } from 'fp-ts/lib/function' import { Invariant3 } from 'fp-ts/lib/Invariant' import { pipe } from 'fp-ts/lib/pipeable' @@ -142,6 +143,12 @@ export function nullable(or: Codec): Codec = make(D.json, E.json) + /** * @category combinators * @since 2.2.15 diff --git a/src/Decoder.ts b/src/Decoder.ts index f98ff1d23..0dbf8291b 100644 --- a/src/Decoder.ts +++ b/src/Decoder.ts @@ -12,7 +12,8 @@ import { Alt2, Alt2C } from 'fp-ts/lib/Alt' import { Bifunctor2 } from 'fp-ts/lib/Bifunctor' import { Category2 } from 'fp-ts/lib/Category' import * as E from 'fp-ts/lib/Either' -import { identity, Refinement } from 'fp-ts/lib/function' +import { flow, identity, Refinement } from 'fp-ts/lib/function' +import * as J from 'fp-ts/lib/Json' import { Functor2 } from 'fp-ts/lib/Functor' import { MonadThrow2C } from 'fp-ts/lib/MonadThrow' import { pipe } from 'fp-ts/lib/pipeable' @@ -21,6 +22,7 @@ import * as FS from './FreeSemigroup' import * as G from './Guard' import * as K from './Kleisli' import * as S from './Schemable' +import Json = J.Json // ------------------------------------------------------------------------------------- // Kleisli config @@ -226,6 +228,23 @@ export const nullable: (or: Decoder) => Decoder /*#__PURE__*/ K.nullable(M)((u, e) => FS.concat(FS.of(DE.member(0, error(u, 'null'))), FS.of(DE.member(1, e)))) +/** + * @category combinators + * @since 2.2.17 + */ +export const json: Decoder = { + decode: (i) => + pipe( + string.decode(i), + E.chain( + flow( + J.parse, + E.altW(() => failure(i, 'JSON')) + ) + ) + ) +} + /** * @category combinators * @since 2.2.15 diff --git a/src/Encoder.ts b/src/Encoder.ts index 8cf7a0855..476bb0f23 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -10,8 +10,10 @@ */ import { Contravariant2 } from 'fp-ts/lib/Contravariant' import { Category2 } from 'fp-ts/lib/Category' +import { Json } from 'fp-ts/lib/Json' import { memoize, intersect_ } from './Schemable' import { identity } from 'fp-ts/lib/function' +import safeStringify from 'safe-stable-stringify' // ------------------------------------------------------------------------------------- // model @@ -39,6 +41,14 @@ export function nullable(or: Encoder): Encoder { } } +/** + * @category combinators + * @since 2.2.17 + */ +export const json: Encoder = { + encode: safeStringify +} + /** * @category combinators * @since 2.2.15 diff --git a/test/Codec.ts b/test/Codec.ts index 8b228ee39..376d34d09 100644 --- a/test/Codec.ts +++ b/test/Codec.ts @@ -205,6 +205,33 @@ describe('Codec', () => { }) }) + describe('json', () => { + describe('decode', () => { + it('should decode valid JSON', () => { + assert.deepStrictEqual(_.json.decode('null'), D.success(null)) + assert.deepStrictEqual(_.json.decode('"a"'), D.success('a')) + assert.deepStrictEqual(_.json.decode('{"a":1}'), D.success({ a: 1 })) + }) + + it('should reject invalid JSON', () => { + assert.deepStrictEqual(_.json.decode(undefined), D.failure(undefined, 'string')) + assert.deepStrictEqual(_.json.decode('{"a":}'), D.failure('{"a":}', 'JSON')) + }) + }) + + describe('encode', () => { + it('should encode a value', () => { + const circular: any = { ref: null } + circular.ref = circular + + assert.deepStrictEqual(_.json.encode(null), 'null') + assert.deepStrictEqual(_.json.encode('a'), '"a"') + assert.deepStrictEqual(_.json.encode({ a: 1 }), '{"a":1}') + assert.deepStrictEqual(_.json.encode(circular), '{"ref":"[Circular]"}') + }) + }) + }) + describe('struct', () => { describe('decode', () => { it('should decode a valid input', () => { diff --git a/test/Decoder.ts b/test/Decoder.ts index f1323521b..79a64b794 100644 --- a/test/Decoder.ts +++ b/test/Decoder.ts @@ -153,6 +153,19 @@ describe('Decoder', () => { }) }) + describe('json', () => { + it('should decode valid JSON', () => { + assert.deepStrictEqual(_.json.decode('null'), _.success(null)) + assert.deepStrictEqual(_.json.decode('"a"'), _.success('a')) + assert.deepStrictEqual(_.json.decode('{"a":1}'), _.success({ a: 1 })) + }) + + it('should reject invalid JSON', () => { + assert.deepStrictEqual(_.json.decode(undefined), _.failure(undefined, 'string')) + assert.deepStrictEqual(_.json.decode('{"a":}'), _.failure('{"a":}', 'JSON')) + }) + }) + describe('struct', () => { it('should decode a valid input', async () => { const decoder = _.struct({ diff --git a/test/Encoder.ts b/test/Encoder.ts index d4155a63d..264915347 100644 --- a/test/Encoder.ts +++ b/test/Encoder.ts @@ -20,6 +20,16 @@ describe('Encoder', () => { assert.deepStrictEqual(encoder.encode(null), null) }) + it('json', () => { + const circular: any = { ref: null } + circular.ref = circular + + assert.deepStrictEqual(E.json.encode(null), 'null') + assert.deepStrictEqual(E.json.encode('a'), '"a"') + assert.deepStrictEqual(E.json.encode({ a: 1 }), '{"a":1}') + assert.deepStrictEqual(E.json.encode(circular), '{"ref":"[Circular]"}') + }) + it('struct', () => { const encoder = E.struct({ a: H.encoderNumberToString, b: H.encoderBooleanToNumber }) assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 }) diff --git a/tslint.json b/tslint.json index 3c8e16e70..52f211d30 100644 --- a/tslint.json +++ b/tslint.json @@ -6,6 +6,6 @@ "variable-name": false, "ter-indent": false, "strict-type-predicates": false, - "deprecation": true + "deprecation": false } }