diff --git a/index.ts b/index.ts index 0e3a946..3e01764 100644 --- a/index.ts +++ b/index.ts @@ -1 +1,2 @@ +export * from './json/index.js' export * from './lazy/index.js' diff --git a/json/index.d.ts b/json/index.d.ts new file mode 100644 index 0000000..1b527e8 --- /dev/null +++ b/json/index.d.ts @@ -0,0 +1,6 @@ +interface JSON { + parse( + text: string, + reviver?: (this: unknown, key: string, value: unknown) => unknown, + ): unknown; +} diff --git a/json/index.ts b/json/index.ts new file mode 100644 index 0000000..b610502 --- /dev/null +++ b/json/index.ts @@ -0,0 +1,31 @@ +/** + * Safely parse a json string. Result is either a non-nullable unknown or an error. + */ +export const safeJsonParse = (value: string | undefined | null): + { success: true, output: NonNullable } + | { success: false, error: unknown } => { + try { + const output = JSON.parse(value ?? '')// as unknown + return output != null ? { success: true, output } : { success: false, error: 'null' } + } catch (error: unknown) { + return { success: false, error } + } +} + +/** + * Safely parse a json string. Value is either a non-nullable unknown or undefined. + */ +export const maybeJsonParse = (value: string | undefined | null): NonNullable | undefined => { + const r = safeJsonParse(value) + return r.success ? r.output : undefined +} + +/* + * Examples: + * ✅ safeJsonParse('{}') // { success: true, output: {} } + * ✅ safeJsonParse('{ "data": { "id": "01" } }') // { success: true, output: { data: { id: '01' } } } + * ✅ safeJsonParse('[1, 2, 3]') // { success: true, output: [1, 2, 3] } + * ❌ safeJsonParse('{ bar: foo }') // { success: false, error: ... } + * ❌ safeJsonParse('') // { success: false, error: ... } + * ❌ safeJsonParse('null') // { success: false, error: ... } + */ diff --git a/json/jsonParse.test.ts b/json/jsonParse.test.ts new file mode 100644 index 0000000..3051a6a --- /dev/null +++ b/json/jsonParse.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest' +import { maybeJsonParse } from './index.js' + +test.each([ + { input: '{}', expectedOutput: {} }, + { input: '{ "number": 1.07 }', expectedOutput: { number: 1.07 } }, + { input: '{ "string": "abc" }', expectedOutput: { string: 'abc' } }, + { input: '{ "boolean": true }', expectedOutput: { boolean: true } }, + { input: '{ "nested": { "string": "abc" } }', expectedOutput: { nested: { string: 'abc' } } }, + { input: '[]', expectedOutput: [] }, + { input: '[1, 2, 3]', expectedOutput: [1, 2, 3] }, + { input: '""', expectedOutput: '' }, + { input: '1.07', expectedOutput: 1.07 }, + { input: 'true', expectedOutput: true }, +])('can parse what JSON.parse can; %j', ({ input, expectedOutput }) => { + expect(maybeJsonParse(input)).toEqual(expectedOutput) +}) + +test.each([ + { input: '' }, + { input: 'null' }, + { input: 'undefined' }, + { input: '{' }, + { input: ']' }, + { input: '{ bar: foo }' }, + { input: '{ "bar": 1a }' }, +])('is undefined when JSON.parse fails; %j', ({ input }) => { + expect(maybeJsonParse(input)).toBeUndefined() +}) diff --git a/tsconfig.json b/tsconfig.json index e992c95..7188578 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,7 +28,7 @@ /* Libs */ "lib": ["ESNext"], - "types": ["node"], + "types": ["node", "./json/index.d.ts"], "outDir": "./dist" },