json-parse-result
v1.0.0
Published
Zero-dependency, fully-typed safe JSON.parse with Result pattern and fallback API
Maintainers
Readme
json-parse-result
Zero-dependency, fully-typed safe wrapper around JSON.parse. Returns a Result object instead of throwing, with optional fallback and type guard APIs.
Why
JSON.parse throws on any malformed input — including empty strings, undefined, network responses with error bodies, and truncated payloads. Every call site needs a try/catch. This package eliminates that boilerplate with a predictable, typed API.
// Before
let data
try {
data = JSON.parse(raw)
} catch (e) {
data = defaultValue
}
// After
const { data, error } = safeParse(raw)Install
npm install json-parse-resultZero runtime dependencies. Works in Node.js, browsers, and edge runtimes (Cloudflare Workers, Deno, Bun).
Quick Start
import { safeParse, parseOrDefault, isParseSuccess } from 'json-parse-result'
// Result pattern
const result = safeParse<{ id: number }>('{"id": 1}')
if (isParseSuccess(result)) {
console.log(result.data.id) // 1 — fully typed
} else {
console.error(result.error) // SyntaxError | TypeError
}
// Inline fallback
const { data } = safeParse<string[]>('broken', { fallback: [] })
console.log(data) // []
// One-liner default
const value = parseOrDefault<number>('42', 0)
console.log(value) // 42API
safeParse<T>(input, options?): ParseResult<T>
Safely parses input. Accepts unknown — non-string inputs return a TypeError without throwing.
safeParse('{"a":1}') // { data: { a: 1 }, error: null }
safeParse('{bad}') // { data: null, error: SyntaxError }
safeParse(undefined) // { data: null, error: TypeError }
safeParse('null') // { data: null, error: null } ← valid JSON!
safeParse('{bad}', { fallback: [] }) // { data: [], error: null }Options:
| Option | Type | Description |
|---|---|---|
| fallback | T | Returned as data on any failure; sets error to null |
| reviver | (key, value) => unknown | Passed directly to JSON.parse |
parseOrDefault<T>(input, fallback, reviver?): T
Thin wrapper that always returns a value of type T. Never returns null.
parseOrDefault<number[]>('[1,2,3]', []) // [1, 2, 3]
parseOrDefault<number[]>('{bad}', []) // []
parseOrDefault<number[]>(undefined, []) // []isParseSuccess<T>(result): result is ParseSuccess<T>
Type guard for narrowing ParseResult<T> in conditionals.
const result = safeParse<User>(raw)
if (isParseSuccess(result)) {
// result.data is User here
doSomething(result.data)
}Types
type ParseSuccess<T> = { data: T; error: null }
type ParseFailure = { data: null; error: Error }
type ParseResult<T> = ParseSuccess<T> | ParseFailure
type SafeParseOptions<T> = {
reviver?: (key: string, value: unknown) => unknown
fallback?: T
}Edge Cases
| Input | Result |
|---|---|
| 'null' | { data: null, error: null } — valid JSON, parse succeeded |
| '' | { data: null, error: SyntaxError } |
| undefined / null / 42 / {} | { data: null, error: TypeError } |
| fallback provided, parse fails | { data: fallback, error: null } |
| fallback provided, parse succeeds | { data: parsedValue, error: null } |
Why Not simdjson or WASM?
- Universal target: Node.js + browsers + edge runtimes. Native addons are Node-only; WASM incurs JS→WASM string-copy overhead that negates throughput gains for common payload sizes.
- V8 already uses SIMD-accelerated
JSON.parsesince Node 16 / Chrome 91. The overhead ofsafeParseover rawJSON.parseis a single branch and a result object allocation (~5–10 ns on modern hardware). - For streaming or >1 MB payloads, use
stream-json. This package's value is safe error handling, not raw throughput.
License
MIT
