Skip to content

Commit

Permalink
add safe json parse functions
Browse files Browse the repository at this point in the history
  • Loading branch information
andrej-dyck committed Dec 16, 2023
1 parent d9bdc5b commit 2d234d8
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 1 deletion.
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './json/index.js'
export * from './lazy/index.js'
6 changes: 6 additions & 0 deletions json/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface JSON {
parse(
text: string,
reviver?: (this: unknown, key: string, value: unknown) => unknown,
): unknown;
}
31 changes: 31 additions & 0 deletions json/index.ts
Original file line number Diff line number Diff line change
@@ -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<unknown> }
| { 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<unknown> | 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: ... }
*/
29 changes: 29 additions & 0 deletions json/jsonParse.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

/* Libs */
"lib": ["ESNext"],
"types": ["node"],
"types": ["node", "./json/index.d.ts"],

"outDir": "./dist"
},
Expand Down

0 comments on commit 2d234d8

Please sign in to comment.