@awsless/json
Advanced tools
+46
-21
@@ -23,3 +23,15 @@ "use strict"; | ||
| __export(index_exports, { | ||
| $bigfloat: () => $bigfloat, | ||
| $bigint: () => $bigint, | ||
| $binary: () => $binary, | ||
| $date: () => $date, | ||
| $duration: () => $duration, | ||
| $infinity: () => $infinity, | ||
| $map: () => $map, | ||
| $mockdate: () => $mockdate, | ||
| $nan: () => $nan, | ||
| $regexp: () => $regexp, | ||
| $set: () => $set, | ||
| $undefined: () => $undefined, | ||
| $url: () => $url, | ||
| createReplacer: () => createReplacer, | ||
@@ -29,3 +41,3 @@ createReviver: () => createReviver, | ||
| createSafeNumberReviver: () => createSafeNumberReviver, | ||
| parse: () => parse, | ||
| parse: () => parse2, | ||
| patch: () => patch, | ||
@@ -44,3 +56,3 @@ safeNumberParse: () => safeNumberParse, | ||
| is: (v) => v instanceof import_big_float.BigFloat, | ||
| parse: (v) => new import_big_float.BigFloat(v), | ||
| parse: (v) => (0, import_big_float.parse)(v), | ||
| stringify: (v) => v.toString() | ||
@@ -153,7 +165,7 @@ }; | ||
| // src/parse.ts | ||
| var parse = (json, types = {}) => { | ||
| var parse2 = (json, options) => { | ||
| const replacements = []; | ||
| const result = JSON.parse( | ||
| json, | ||
| createReviver(types, (target, key, value) => { | ||
| createReviver(options?.types, (target, key, value) => { | ||
| replacements.push([target, key, value]); | ||
@@ -183,8 +195,6 @@ }) | ||
| return type.parse(stringified); | ||
| } else if (registerReplacement) { | ||
| } else { | ||
| const result = type.replace(stringified); | ||
| registerReplacement(this, key, result); | ||
| registerReplacement?.(this, key, result); | ||
| return result; | ||
| } else { | ||
| return type.replace(stringified); | ||
| } | ||
@@ -199,12 +209,15 @@ } | ||
| // src/stringify.ts | ||
| var stringify = (value, types = {}) => { | ||
| return JSON.stringify(value, createReplacer(types)); | ||
| var stringify = (value, options) => { | ||
| return JSON.stringify(value, createReplacer(options)); | ||
| }; | ||
| var createReplacer = (types = {}) => { | ||
| types = { | ||
| var createReplacer = (options) => { | ||
| const types = { | ||
| ...baseTypes, | ||
| ...types | ||
| ...options?.types | ||
| }; | ||
| return function(key, value) { | ||
| const original = this[key]; | ||
| if (!options?.preserveUndefinedValues && key && typeof original === "undefined" && typeof this === "object" && !Array.isArray(this)) { | ||
| return value; | ||
| } | ||
| for (const [typeName, type] of Object.entries(types)) { | ||
@@ -223,3 +236,3 @@ if (type.is(original)) { | ||
| var patch = (value, types = {}) => { | ||
| return parse(JSON.stringify(value), types); | ||
| return parse2(JSON.stringify(value), types); | ||
| }; | ||
@@ -235,9 +248,2 @@ var unpatch = (value, types = {}) => { | ||
| // src/type/mockdate.ts | ||
| var $mockdate = { | ||
| is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function", | ||
| parse: (v) => new Date(v), | ||
| stringify: (v) => v.toISOString() | ||
| }; | ||
| // src/safe-number/parse.ts | ||
@@ -273,5 +279,24 @@ var safeNumberParse = (json, props) => { | ||
| }; | ||
| // src/type/mockdate.ts | ||
| var $mockdate = { | ||
| is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function", | ||
| parse: (v) => new Date(v), | ||
| stringify: (v) => v.toISOString() | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| $bigfloat, | ||
| $bigint, | ||
| $binary, | ||
| $date, | ||
| $duration, | ||
| $infinity, | ||
| $map, | ||
| $mockdate, | ||
| $nan, | ||
| $regexp, | ||
| $set, | ||
| $undefined, | ||
| $url, | ||
| createReplacer, | ||
@@ -278,0 +303,0 @@ createReviver, |
+40
-6
@@ -0,1 +1,4 @@ | ||
| import { BigFloat } from '@awsless/big-float'; | ||
| import { Duration } from '@awsless/duration'; | ||
| type Serializable<I, O> = { | ||
@@ -14,14 +17,19 @@ is: (value: unknown) => boolean; | ||
| declare const parse: (json: string, types?: SerializableTypes) => any; | ||
| type Options$1 = { | ||
| types?: SerializableTypes; | ||
| }; | ||
| declare const parse: (json: string, options?: Options$1) => any; | ||
| type Reviver$1 = (this: any, key: string, value: any) => any; | ||
| declare const createReviver: (types?: SerializableTypes, registerReplacement?: (target: any, key: string, value: unknown) => void) => Reviver$1; | ||
| declare const stringify: (value: unknown, types?: SerializableTypes) => string; | ||
| type Options = { | ||
| types?: SerializableTypes; | ||
| preserveUndefinedValues?: boolean; | ||
| }; | ||
| declare const stringify: (value: unknown, options?: Options) => string; | ||
| type Replacer$1 = (this: any, key: string, value: any) => any; | ||
| declare const createReplacer: (types?: SerializableTypes) => Replacer$1; | ||
| declare const createReplacer: (options?: Options) => Replacer$1; | ||
| declare const setGlobalTypes: (types: SerializableTypes) => void; | ||
| declare const $mockdate: Serializable<Date, string>; | ||
| type Props$1 = { | ||
@@ -44,2 +52,28 @@ parse: (value: string) => unknown; | ||
| export { $mockdate, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch }; | ||
| declare const $bigfloat: Serializable<BigFloat, string>; | ||
| declare const $bigint: Serializable<bigint, string>; | ||
| declare const $binary: Serializable<Uint8Array, string>; | ||
| declare const $date: Serializable<Date, string>; | ||
| declare const $duration: Serializable<Duration, string>; | ||
| declare const $infinity: Serializable<typeof Infinity, 1 | 0>; | ||
| declare const $map: Serializable<Map<unknown, unknown>, [unknown, unknown][]>; | ||
| declare const $mockdate: Serializable<Date, string>; | ||
| declare const $nan: Serializable<typeof NaN, 0>; | ||
| declare const $regexp: Serializable<RegExp, [string, string]>; | ||
| declare const $set: Serializable<Set<unknown>, unknown[]>; | ||
| declare const $undefined: Serializable<undefined, 0>; | ||
| declare const $url: Serializable<URL, string>; | ||
| export { $bigfloat, $bigint, $binary, $date, $duration, $infinity, $map, $mockdate, $nan, $regexp, $set, $undefined, $url, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch }; |
+40
-6
@@ -0,1 +1,4 @@ | ||
| import { BigFloat } from '@awsless/big-float'; | ||
| import { Duration } from '@awsless/duration'; | ||
| type Serializable<I, O> = { | ||
@@ -14,14 +17,19 @@ is: (value: unknown) => boolean; | ||
| declare const parse: (json: string, types?: SerializableTypes) => any; | ||
| type Options$1 = { | ||
| types?: SerializableTypes; | ||
| }; | ||
| declare const parse: (json: string, options?: Options$1) => any; | ||
| type Reviver$1 = (this: any, key: string, value: any) => any; | ||
| declare const createReviver: (types?: SerializableTypes, registerReplacement?: (target: any, key: string, value: unknown) => void) => Reviver$1; | ||
| declare const stringify: (value: unknown, types?: SerializableTypes) => string; | ||
| type Options = { | ||
| types?: SerializableTypes; | ||
| preserveUndefinedValues?: boolean; | ||
| }; | ||
| declare const stringify: (value: unknown, options?: Options) => string; | ||
| type Replacer$1 = (this: any, key: string, value: any) => any; | ||
| declare const createReplacer: (types?: SerializableTypes) => Replacer$1; | ||
| declare const createReplacer: (options?: Options) => Replacer$1; | ||
| declare const setGlobalTypes: (types: SerializableTypes) => void; | ||
| declare const $mockdate: Serializable<Date, string>; | ||
| type Props$1 = { | ||
@@ -44,2 +52,28 @@ parse: (value: string) => unknown; | ||
| export { $mockdate, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch }; | ||
| declare const $bigfloat: Serializable<BigFloat, string>; | ||
| declare const $bigint: Serializable<bigint, string>; | ||
| declare const $binary: Serializable<Uint8Array, string>; | ||
| declare const $date: Serializable<Date, string>; | ||
| declare const $duration: Serializable<Duration, string>; | ||
| declare const $infinity: Serializable<typeof Infinity, 1 | 0>; | ||
| declare const $map: Serializable<Map<unknown, unknown>, [unknown, unknown][]>; | ||
| declare const $mockdate: Serializable<Date, string>; | ||
| declare const $nan: Serializable<typeof NaN, 0>; | ||
| declare const $regexp: Serializable<RegExp, [string, string]>; | ||
| declare const $set: Serializable<Set<unknown>, unknown[]>; | ||
| declare const $undefined: Serializable<undefined, 0>; | ||
| declare const $url: Serializable<URL, string>; | ||
| export { $bigfloat, $bigint, $binary, $date, $duration, $infinity, $map, $mockdate, $nan, $regexp, $set, $undefined, $url, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch }; |
+35
-22
| // src/type/bigfloat.ts | ||
| import { BigFloat } from "@awsless/big-float"; | ||
| import { BigFloat, parse } from "@awsless/big-float"; | ||
| var $bigfloat = { | ||
| is: (v) => v instanceof BigFloat, | ||
| parse: (v) => new BigFloat(v), | ||
| parse: (v) => parse(v), | ||
| stringify: (v) => v.toString() | ||
@@ -113,7 +113,7 @@ }; | ||
| // src/parse.ts | ||
| var parse = (json, types = {}) => { | ||
| var parse2 = (json, options) => { | ||
| const replacements = []; | ||
| const result = JSON.parse( | ||
| json, | ||
| createReviver(types, (target, key, value) => { | ||
| createReviver(options?.types, (target, key, value) => { | ||
| replacements.push([target, key, value]); | ||
@@ -143,8 +143,6 @@ }) | ||
| return type.parse(stringified); | ||
| } else if (registerReplacement) { | ||
| } else { | ||
| const result = type.replace(stringified); | ||
| registerReplacement(this, key, result); | ||
| registerReplacement?.(this, key, result); | ||
| return result; | ||
| } else { | ||
| return type.replace(stringified); | ||
| } | ||
@@ -159,12 +157,15 @@ } | ||
| // src/stringify.ts | ||
| var stringify = (value, types = {}) => { | ||
| return JSON.stringify(value, createReplacer(types)); | ||
| var stringify = (value, options) => { | ||
| return JSON.stringify(value, createReplacer(options)); | ||
| }; | ||
| var createReplacer = (types = {}) => { | ||
| types = { | ||
| var createReplacer = (options) => { | ||
| const types = { | ||
| ...baseTypes, | ||
| ...types | ||
| ...options?.types | ||
| }; | ||
| return function(key, value) { | ||
| const original = this[key]; | ||
| if (!options?.preserveUndefinedValues && key && typeof original === "undefined" && typeof this === "object" && !Array.isArray(this)) { | ||
| return value; | ||
| } | ||
| for (const [typeName, type] of Object.entries(types)) { | ||
@@ -183,3 +184,3 @@ if (type.is(original)) { | ||
| var patch = (value, types = {}) => { | ||
| return parse(JSON.stringify(value), types); | ||
| return parse2(JSON.stringify(value), types); | ||
| }; | ||
@@ -195,9 +196,2 @@ var unpatch = (value, types = {}) => { | ||
| // src/type/mockdate.ts | ||
| var $mockdate = { | ||
| is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function", | ||
| parse: (v) => new Date(v), | ||
| stringify: (v) => v.toISOString() | ||
| }; | ||
| // src/safe-number/parse.ts | ||
@@ -233,4 +227,23 @@ var safeNumberParse = (json, props) => { | ||
| }; | ||
| // src/type/mockdate.ts | ||
| var $mockdate = { | ||
| is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function", | ||
| parse: (v) => new Date(v), | ||
| stringify: (v) => v.toISOString() | ||
| }; | ||
| export { | ||
| $bigfloat, | ||
| $bigint, | ||
| $binary, | ||
| $date, | ||
| $duration, | ||
| $infinity, | ||
| $map, | ||
| $mockdate, | ||
| $nan, | ||
| $regexp, | ||
| $set, | ||
| $undefined, | ||
| $url, | ||
| createReplacer, | ||
@@ -240,3 +253,3 @@ createReviver, | ||
| createSafeNumberReviver, | ||
| parse, | ||
| parse2 as parse, | ||
| patch, | ||
@@ -243,0 +256,0 @@ safeNumberParse, |
+5
-5
| { | ||
| "name": "@awsless/json", | ||
| "version": "0.0.10", | ||
| "version": "0.0.11", | ||
| "license": "MIT", | ||
@@ -44,8 +44,8 @@ "type": "module", | ||
| "peerDependencies": { | ||
| "@awsless/big-float": "^0.0.6", | ||
| "@awsless/duration": "^0.0.3" | ||
| "@awsless/big-float": "^0.1.5", | ||
| "@awsless/duration": "^0.0.4" | ||
| }, | ||
| "devDependencies": { | ||
| "@awsless/duration": "^0.0.3", | ||
| "@awsless/big-float": "^0.0.6" | ||
| "@awsless/big-float": "^0.1.5", | ||
| "@awsless/duration": "^0.0.4" | ||
| }, | ||
@@ -52,0 +52,0 @@ "scripts": { |
+42
-18
@@ -1,2 +0,1 @@ | ||
| # @awsless/json | ||
@@ -7,11 +6,13 @@ | ||
| Features: | ||
| - Lightweight / Using the JS native JSON parser. | ||
| - JSON backwards compatible. | ||
| - No precision loss. | ||
| - Includes support for basic JS types. | ||
| - Extendable. | ||
| - Lightweight / Using the JS native JSON parser. | ||
| - JSON backwards compatible. | ||
| - No precision loss. | ||
| - Includes support for basic JS types. | ||
| - Extendable. | ||
| ## The Problem | ||
| JSON doesn't have support for types like: | ||
| - `undefined` | ||
@@ -37,3 +38,3 @@ - `NaN` | ||
| ```ts | ||
| import { parse, stringify } from '@awsless/json'; | ||
| import { parse, stringify } from '@awsless/json' | ||
@@ -52,3 +53,3 @@ // The output will be {"$bigint":"1"} | ||
| ```ts | ||
| import { stringify, patch } from '@awsless/json'; | ||
| import { stringify, patch } from '@awsless/json' | ||
@@ -69,3 +70,3 @@ const json = stringify(1n) | ||
| ```ts | ||
| import { parse, stringify, Serializable } from '@awsless/json'; | ||
| import { parse, stringify, Serializable, setGlobalTypes } from '@awsless/json' | ||
@@ -87,8 +88,31 @@ class Custom { | ||
| // Stringify your custom type. | ||
| const json = stringify(new Custom('example'), { $custom }) | ||
| const json = stringify(new Custom('example'), { | ||
| types: { $custom }, | ||
| }) | ||
| // Parse the json with your custom type. | ||
| const value = parse(json, { $custom }) | ||
| const value = parse(json, { | ||
| types: { $custom }, | ||
| }) | ||
| // Additionally, you can globally add your own extensions. | ||
| setGlobalTypes({ $custom }) | ||
| ``` | ||
| ## Preserve undefined object properties | ||
| The native `JSON.stringify` will strip undefined object properties to generate smaller JSON output. We do the same thing but we have a special option to opt out of this behavior if correctness is important to you. | ||
| ```ts | ||
| const input = { key: undefined } | ||
| const smallerJson = stringify(input, { | ||
| preserveUndefinedValues: true, | ||
| }) // {} | ||
| const moreCorrectJson = stringify(input, { | ||
| preserveUndefinedValues: true, | ||
| }) // {"key":{"$undefined":0}} | ||
| ``` | ||
| ## Precision Loss | ||
@@ -99,11 +123,11 @@ | ||
| > [!NOTE] | ||
| > These utility functions will only work in environments that support `JSON.rawJSON` | ||
| > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/rawJSON#browser_compatibility | ||
| > These utility functions will only work in environments that support `JSON.rawJSON` > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/rawJSON#browser_compatibility | ||
| ```ts | ||
| import { safeNumberParse, safeNumberStringify } from '@awsless/json'; | ||
| import { BigFloat, eq } from '@awsless/big-float'; | ||
| import { safeNumberParse, safeNumberStringify } from '@awsless/json' | ||
| import { BigFloat, eq } from '@awsless/big-float' | ||
| const value = new BigFloat(1) | ||
| const json = safeNumberStringify(ONE, { | ||
| const one = new BigFloat(1) | ||
| const json = safeNumberStringify(one, { | ||
| is: v => v instanceof BigFloat, | ||
@@ -119,3 +143,3 @@ stringify: v => v.toString(), | ||
| console.log(eq(value, result)) // true | ||
| console.log(eq(one, result)) // true | ||
| ``` | ||
@@ -122,0 +146,0 @@ |
24233
16.3%575
11.65%145
19.83%