From 94d250311b6035de2b8700e7eaf7c9ba0b4639fc Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 25 Oct 2023 12:49:17 +0200 Subject: [PATCH] fix: replace `JavaScriptValue` with `unknown`, which is more accurate (see #250) --- README.md | 12 ++++++------ src/parse.ts | 4 ++-- src/revive.ts | 6 +----- src/stringify.ts | 4 ++-- src/types.ts | 22 ++++++---------------- test/parse.test.ts | 12 ++++++------ test/stringify.test.ts | 18 +++++++++--------- 7 files changed, 32 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 74069c6..8116d09 100644 --- a/README.md +++ b/README.md @@ -217,11 +217,11 @@ The `LosslessJSON.parse()` function parses a string as JSON, optionally transfor - **@param** `{string} text` The string to parse as JSON. See the JSON object for a description of JSON syntax. -- **@param** `{(key: string, value: JSONValue) => JavaScriptValue} [reviver]` +- **@param** `{(key: string, value: JSONValue) => unknown} [reviver]` If a function, prescribes how the value originally produced by parsing is transformed, before being returned. -- **@param** `{function(value: string) : JavaScriptValue} [parseNumber]` +- **@param** `{function(value: string) : unknown} [parseNumber]` Pass an optional custom number parser. Input is a string, and the output can be any numeric value: `number`, `bigint`, `LosslessNumber`, or a custom `BigNumber` library. By default, all numeric values are parsed into a `LosslessNumber`. -- **@returns** `{JavaScriptValue}` +- **@returns** `{unknown}` Returns the Object corresponding to the given JSON text. - **@throws** Throws a SyntaxError exception if the string to parse is not valid JSON. @@ -229,13 +229,13 @@ The `LosslessJSON.parse()` function parses a string as JSON, optionally transfor The `LosslessJSON.stringify()` function converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified. -- **@param** `{JavaScriptValue} value` +- **@param** `{unknown} value` The value to convert to a JSON string. -- **@param** `{((key: string, value: JavaScriptValue) => JSONValue) | Array.} [replacer]` +- **@param** `{((key: string, value: unknown) => unknown) | Array.} [replacer]` A function that alters the behavior of the stringification process, or an array with strings or numbers that serve as a whitelist for selecting the properties of the value object to be included in the JSON string. If this value is `null` or not provided, all properties of the object are included in the resulting JSON string. - **@param** `{number | string | undefined} [space]` A `string` or `number` that is used to insert white space into the output JSON string for readability purposes. If this is a `number`, it indicates the number of space characters to use as white space. Values less than 1 indicate that no space should be used. If this is a `string`, the `string` is used as white space. If this parameter is not provided (or is `null`), no white space is used. -- **@param** `{Array<{test: (value: JavaScriptValue) => boolean, stringify: (value: JavaScriptValue) => string}>} [numberStringifiers]` +- **@param** `{Array<{test: (value: unknown) => boolean, stringify: (value: unknown) => string}>} [numberStringifiers]` An optional list with additional number stringifiers, for example to serialize a `BigNumber`. The output of the function must be valid stringified JSON number. When `undefined` is returned, the property will be deleted from the object. The difference with using a `replacer` is that the output of a `replacer` must be JSON and will be stringified afterwards, whereas the output of the `numberStringifiers` is already stringified JSON. - **@returns** `{string | undefined}` Returns the string representation of the JSON object. diff --git a/src/parse.ts b/src/parse.ts index 8b58740..e91f35a 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,7 +1,7 @@ import { parseLosslessNumber } from './numberParsers.js' import { revive } from './revive.js' import type { NumberParser, Reviver } from './types' -import { GenericObject, JavaScriptValue } from './types' +import { GenericObject } from './types' /** * The LosslessJSON.parse() method parses a string as JSON, optionally transforming @@ -29,7 +29,7 @@ export function parse( text: string, reviver?: Reviver, parseNumber: NumberParser = parseLosslessNumber -): JavaScriptValue { +): unknown { let i = 0 const value = parseValue() expectValue(value) diff --git a/src/revive.ts b/src/revive.ts index 287ae47..4404722 100644 --- a/src/revive.ts +++ b/src/revive.ts @@ -29,11 +29,7 @@ function reviveValue( } else if (value && typeof value === 'object' && !isLosslessNumber(value)) { // note the special case for LosslessNumber, // we don't want to iterate over the internals of a LosslessNumber - return reviver.call( - context, - key, - reviveObject(value as unknown as GenericObject, reviver) - ) + return reviver.call(context, key, reviveObject(value as GenericObject, reviver)) } else { return reviver.call(context, key, value) } diff --git a/src/stringify.ts b/src/stringify.ts index 13dbf3f..fa50b5c 100644 --- a/src/stringify.ts +++ b/src/stringify.ts @@ -1,4 +1,4 @@ -import type { GenericObject, Replacer, NumberStringifier, JavaScriptValue } from './types' +import type { GenericObject, NumberStringifier, Replacer } from './types' import { isNumber } from './utils.js' /** @@ -36,7 +36,7 @@ import { isNumber } from './utils.js' * @returns Returns the string representation of the JSON object. */ export function stringify( - value: JavaScriptValue, + value: unknown, replacer?: Replacer, space?: number | string, numberStringifiers?: NumberStringifier[] diff --git a/src/types.ts b/src/types.ts index c030e63..57948f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,27 +6,17 @@ export type JSONValue = export type JSONObject = { [key: string]: JSONValue } export type JSONArray = JSONValue[] -export type JavaScriptPrimitive = string | number | boolean | null | bigint | Date | unknown -export type JavaScriptValue = - | { [key: string]: JavaScriptValue } // object - | JavaScriptValue[] // array - | JavaScriptPrimitive -export type JavaScriptObject = { [key: string]: JavaScriptValue } -export type JavaScriptArray = JavaScriptValue[] +export type Reviver = (key: string, value: JSONValue) => unknown -export type Reviver = (key: string, value: JSONValue) => JavaScriptValue - -export type NumberParser = (value: string) => JavaScriptValue +export type NumberParser = (value: string) => unknown export type Replacer = - | ((key: string, value: JavaScriptObject) => JSONValue | undefined) + | ((key: string, value: unknown) => unknown | undefined) | Array export interface NumberStringifier { - test: (value: JavaScriptValue) => boolean - stringify: (value: JavaScriptValue) => string + test: (value: unknown) => boolean + stringify: (value: unknown) => string } -export type GenericObject = { - [key: string]: T -} +export type GenericObject = Record diff --git a/test/parse.test.ts b/test/parse.test.ts index 81d6867..d6a0abd 100644 --- a/test/parse.test.ts +++ b/test/parse.test.ts @@ -7,7 +7,7 @@ import { reviveDate, stringify } from '../src' -import { GenericObject, JSONValue } from '../src/types' +import { GenericObject } from '../src/types' import { isDeepEqual } from '../src/parse' // helper function to create a lossless number @@ -21,7 +21,7 @@ function expectDeepEqual(a: unknown, b: unknown) { } // turn a JavaScript object into plain JSON -function jsonify(obj: unknown): JSONValue { +function jsonify(obj: unknown): unknown { return JSON.parse(JSON.stringify(obj)) } @@ -115,7 +115,7 @@ test('reviver - replace values', function () { } } - function reviver(key: string, value: JSONValue) { + function reviver(key: string, value: unknown) { return { type: typeof value, value @@ -129,9 +129,9 @@ test('reviver - invoke callbacks with key/value and correct context', function ( const text = '{"a":123,"b":"str","c":null,"22":22,"d":false,"e":[1,2,3]}' interface Log { - context: JSONValue + context: unknown key: string - value: JSONValue + value: unknown } const expected: Log[] = [ @@ -192,7 +192,7 @@ test('reviver - invoke callbacks with key/value and correct context', function ( return JSON.parse(stringify(json)) } - function reviver(key: string, value: JSONValue) { + function reviver(key: string, value: unknown): unknown { return key === 'd' ? undefined : key === '1' ? null : value } diff --git a/test/stringify.test.ts b/test/stringify.test.ts index 189b787..ec20f86 100644 --- a/test/stringify.test.ts +++ b/test/stringify.test.ts @@ -1,6 +1,6 @@ import Decimal from 'decimal.js' import { LosslessNumber, stringify } from '../src' -import type { GenericObject, JSONValue } from '../src/types' +import type { GenericObject } from '../src/types' // helper function to create a lossless number function lln(value: string) { @@ -151,9 +151,9 @@ test('stringify with replacer function', function () { const json: GenericObject = { a: 123, b: 'str', c: null, d: false, e: [1, 2, 3] } interface Log { - context: JSONValue + context: unknown key: string - value: JSONValue + value: unknown } const expected: Log[] = [ @@ -206,8 +206,8 @@ test('stringify with replacer function', function () { const logs: Log[] = [] stringify(json, function (key, value) { - logs.push({ context: this, key, value: value as JSONValue }) - return value as JSONValue + logs.push({ context: this, key, value }) + return value }) expect(logs).toEqual(expected) @@ -225,7 +225,7 @@ test('stringify with replacer function (2)', function () { const expected = '{"a":"number:a:123","b":"string:b:str"}' - function replacer(key: string, value: unknown): JSONValue { + function replacer(key: string, value: unknown): unknown { if (key === 'c') { return undefined } @@ -237,7 +237,7 @@ test('stringify with replacer function (2)', function () { return 'string:' + key + ':' + value } - return value as JSONValue + return value } expect(stringify(json, replacer)).toEqual(expected) @@ -258,7 +258,7 @@ test('stringify with replacer Array', function () { }) test('stringify with numeric space', function () { - const json: JSONValue = { a: 1, b: [1, 2, null, undefined, { c: 3 }], d: null } + const json: unknown = { a: 1, b: [1, 2, null, undefined, { c: 3 }], d: null } const expected = '{\n' + @@ -282,7 +282,7 @@ test('stringify with numeric space', function () { }) test('stringify with string space', function () { - const json: JSONValue = { a: 1, b: [1, 2, null, undefined, { c: 3 }], d: null } + const json: unknown = { a: 1, b: [1, 2, null, undefined, { c: 3 }], d: null } const expected = '{\n' +