Skip to content

Commit

Permalink
fix: improve type definition
Browse files Browse the repository at this point in the history
The current typescript definition always indicated a string
as return type. That is faulty for values such as `undefined` or symbol
values.
The new return type determins the correct return type depending on the
input type.

Fixes: #35
  • Loading branch information
BridgeAR committed Oct 18, 2022
1 parent 73a49c2 commit b1e3834
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 61 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v2.4.1

- More precise TypeScript types. The return type is now either `string`, `undefined` or `string | undefined` depending on the input.

## v2.4.0

- Added `strict` option to verify that the passed in objects are fully compatible with JSON without removing information. If not, an error is thrown.
Expand Down
20 changes: 1 addition & 19 deletions esm/wrapper.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1 @@
export function stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string;
export function stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string;

export interface StringifyOptions {
bigint?: boolean,
circularValue?: string | null | TypeErrorConstructor | ErrorConstructor,
deterministic?: boolean,
maximumBreadth?: number,
maximumDepth?: number,
strict?: boolean,
}

export namespace stringify {
export function configure(options: StringifyOptions): typeof stringify;
}

export function configure(options: StringifyOptions): typeof stringify;

export default stringify;
export * from '../index.d'
13 changes: 8 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export function stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string;
export function stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string;
export type Replacer = (number | string)[] | null | undefined | ((key: string, value: unknown) => string | number | boolean | null)

export function stringify(value: undefined | symbol | ((...args: unknown[]) => unknown), replacer?: Replacer, space?: string | number): undefined
export function stringify(value: string | number | unknown[] | null | boolean | object, replacer?: Replacer, space?: string | number): string
export function stringify(value: unknown, replacer?: ((key: string, value: unknown) => unknown) | (number | string)[] | null | undefined, space?: string | number): string | undefined

export interface StringifyOptions {
bigint?: boolean,
Expand All @@ -11,9 +14,9 @@ export interface StringifyOptions {
}

export namespace stringify {
export function configure(options: StringifyOptions): typeof stringify;
export function configure(options: StringifyOptions): typeof stringify
}

export function configure(options: StringifyOptions): typeof stringify;
export function configure(options: StringifyOptions): typeof stringify

export default stringify;
export default stringify
11 changes: 5 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ exports.configure = configure

module.exports = stringify

// eslint-disable-next-line
// eslint-disable-next-line no-control-regex
const strEscapeSequencesRegExp = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/
// eslint-disable-next-line
const strEscapeSequencesReplacer = new RegExp(strEscapeSequencesRegExp, 'g')

// Escaped special characters. Use empty strings to fill up unused entries.
Expand Down Expand Up @@ -130,7 +129,7 @@ function stringifyTypedArray (array, separator, maximumBreadth) {
}

function getCircularValueOption (options) {
if (options && hasOwnProperty.call(options, 'circularValue')) {
if (hasOwnProperty.call(options, 'circularValue')) {
const circularValue = options.circularValue
if (typeof circularValue === 'string') {
return `"${circularValue}"`
Expand All @@ -152,7 +151,7 @@ function getCircularValueOption (options) {

function getBooleanOption (options, key) {
let value
if (options && hasOwnProperty.call(options, key)) {
if (hasOwnProperty.call(options, key)) {
value = options[key]
if (typeof value !== 'boolean') {
throw new TypeError(`The "${key}" argument must be of type boolean`)
Expand All @@ -163,7 +162,7 @@ function getBooleanOption (options, key) {

function getPositiveIntegerOption (options, key) {
let value
if (options && hasOwnProperty.call(options, key)) {
if (hasOwnProperty.call(options, key)) {
value = options[key]
if (typeof value !== 'number') {
throw new TypeError(`The "${key}" argument must be of type number`)
Expand Down Expand Up @@ -196,7 +195,7 @@ function getUniqueReplacerSet (replacerArray) {
}

function getStrictOption (options) {
if (options && hasOwnProperty.call(options, 'strict')) {
if (hasOwnProperty.call(options, 'strict')) {
const value = options.strict
if (typeof value !== 'boolean') {
throw new TypeError('The "strict" argument must be of type boolean')
Expand Down
69 changes: 38 additions & 31 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,13 +346,17 @@ test('replacer removing all elements and indentation', function (assert) {
test('array replacer', function (assert) {
const replacer = ['f', 1, null]
const obj = { f: null, null: true, 1: false }
// The null element will be removed!
// The null element will be ignored!
// @ts-expect-error
const expected = JSON.stringify(obj, replacer)
// @ts-expect-error
let actual = stringify(obj, replacer)
assert.equal(actual, expected)

// @ts-expect-error
obj.f = obj

// @ts-expect-error
actual = stringify({ toJSON () { return obj } }, replacer)
assert.equal(actual, expected.replace('null', '"[Circular]"'))

Expand All @@ -374,7 +378,9 @@ test('array replacer and indentation', function (assert) {
const replacer = ['f', 1, null]
const obj = { f: null, null: true, 1: [false, -Infinity, 't'] }
// The null element will be removed!
// @ts-expect-error
const expected = JSON.stringify(obj, replacer, 2)
// @ts-expect-error
const actual = stringify(obj, replacer, 2)
assert.equal(actual, expected)
assert.end()
Expand Down Expand Up @@ -484,16 +490,16 @@ test('object with undefined values', function (assert) {
})

test('undefined values and indented', function (assert) {
let obj = { a: 1, c: undefined, b: 'hello' }
const obj1 = { a: 1, c: undefined, b: 'hello' }

let expected = JSON.stringify(obj, null, 2)
let actual = stringify(obj, null, 2)
let expected = JSON.stringify(obj1, null, 2)
let actual = stringify(obj1, null, 2)
assert.equal(actual, expected)

obj = { b: 'hello', a: undefined, c: 1 }
const obj2 = { b: 'hello', a: undefined, c: 1 }

expected = JSON.stringify(obj)
actual = stringify(obj)
expected = JSON.stringify(obj2)
actual = stringify(obj2)
assert.equal(actual, expected)

assert.end()
Expand All @@ -516,6 +522,7 @@ test('bigint option', function (assert) {
assert.equal(actualBigInt, expectedBigInt)
assert.equal(actualDefault, expectedBigInt)

// @ts-expect-error
assert.throws(() => stringify.configure({ bigint: null }), /bigint/)

assert.end()
Expand Down Expand Up @@ -620,11 +627,11 @@ test('non-deterministic with replacer', function (assert) {
const keys = Object.keys(obj)

const expected = stringify(obj, ['b', 'c', 'd', 'e'])
let actual = stringifyNonDeterministic(obj, keys)
assert.equal(actual, expected)
const actualA = stringifyNonDeterministic(obj, keys)
assert.equal(actualA, expected)

actual = stringifyNonDeterministic(obj, (k, v) => v)
assert.equal(actual, expected)
const actualB = stringifyNonDeterministic(obj, (k, v) => v)
assert.equal(actualB, expected)

assert.end()
})
Expand Down Expand Up @@ -654,20 +661,20 @@ test('check small typed arrays with extra properties', function (assert) {
// @ts-expect-error
obj.foo = true
let expected = JSON.stringify(obj)
let actual = stringify(obj)
assert.equal(actual, expected)
const actualA = stringify(obj)
assert.equal(actualA, expected)

expected = JSON.stringify(obj, null, 2)
actual = stringify(obj, null, 2)
assert.equal(actual, expected)
const actualB = stringify(obj, null, 2)
assert.equal(actualB, expected)

expected = JSON.stringify(obj, ['foo'])
actual = stringify(obj, ['foo'])
assert.equal(actual, expected)
const actualC = stringify(obj, ['foo'])
assert.equal(actualC, expected)

expected = JSON.stringify(obj, (a, b) => b)
actual = stringify(obj, (a, b) => b)
assert.equal(actual, expected)
const actualD = stringify(obj, (a, b) => b)
assert.equal(actualD, expected)

assert.end()
})
Expand Down Expand Up @@ -972,26 +979,26 @@ test('show skipped keys even non were serliazable', (assert) => {

const input = { a: Symbol('ignored'), b: Symbol('ignored') }

let actual = serialize(input)
const actual1 = serialize(input)
let expected = '{"...":"1 item not stringified"}'
assert.equal(actual, expected)
assert.equal(actual1, expected)

actual = serialize(input, (a, b) => b)
assert.equal(actual, expected)
const actual2 = serialize(input, (a, b) => b)
assert.equal(actual2, expected)

actual = serialize(input, null, 1)
const actual3 = serialize(input, null, 1)
expected = '{\n "...": "1 item not stringified"\n}'
assert.equal(actual, expected)
assert.equal(actual3, expected)

actual = serialize(input, (a, b) => b, 1)
assert.equal(actual, expected)
const actual4 = serialize(input, (a, b) => b, 1)
assert.equal(actual4, expected)

actual = serialize(input, ['a'])
const actual5 = serialize(input, ['a'])
expected = '{}'
assert.equal(actual, expected)
assert.equal(actual5, expected)

actual = serialize(input, ['a', 'b', 'c'])
assert.equal(actual, expected)
const actual6 = serialize(input, ['a', 'b', 'c'])
assert.equal(actual6, expected)

assert.end()
})
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"importHelpers": false,
"target": "esnext",
"module": "CommonJS",
"strict": true,
"noImplicitAny": false,
"lib": [
"esnext",
"dom"
Expand Down

0 comments on commit b1e3834

Please sign in to comment.