Files
2026-05-27 23:47:53 +01:00

124 lines
5.5 KiB
Markdown

# @suaveplan/json - Safe JSON parse/stringify with prototype-pollution protection, circular-ref detection, and OTel instrumentation
## Summary
Safe, enterprise-grade JSON utilities with prototype-pollution protection,
circular-reference detection, and OpenTelemetry instrumentation.
<!-- begin:auto-docs -->
## Documentation tree
- [errors](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [json-error](./src/errors/json-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_ - [safe-json](./src/safe-json/safe-json.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ - [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_
## Install
```bash
bun add @suaveplan/json
```
## Quick start
```ts
import { safeParse, safeStringify, tryParse, isValidJSON, getStringifySize } from "@suaveplan/json";
// Safe parse — throws JsonError on invalid JSON or forbidden keys
const obj = safeParse<{ a: number }>('{"a": 1}');
// Safe stringify — throws JsonError on circular references
const json = safeStringify(obj);
// Non-throwing parse
const val = tryParse("bad input"); // undefined
// Validity check (handles "null" correctly)
isValidJSON("null"); // true
isValidJSON("bad"); // false
// UTF-8 byte size of JSON representation
getStringifySize({ hello: "world" }); // 15
```
## Modules
| Module | Description |
|--------|-------------|
| `@suaveplan/json` | Main entry — all public exports |
| `@suaveplan/json/safe-json` | `safeParse`, `safeStringify`, `tryParse`, `isValidJSON`, `getStringifySize` |
| `@suaveplan/json/errors` | `JsonError`, `JSON_CODES`, `JsonCode` |
## Error handling
Every failure is a structured `JsonError` with a stable code:
| Code | Trigger |
|------|---------|
| `JSON.PARSE.FAILED` | Invalid JSON syntax or aborted `AbortSignal` |
| `JSON.PARSE.PROTOTYPE_POLLUTION` | Forbidden key (`__proto__`, `constructor`, `prototype`) |
| `JSON.STRINGIFY.CIRCULAR` | True circular reference detected |
| `JSON.STRINGIFY.FAILED` | Aborted signal or non-serialisable value (e.g. `BigInt`) |
```ts
import { safeParse, JsonError, JSON_CODES } from "@suaveplan/json";
try {
safeParse('{"__proto__": {}}');
} catch (err) {
if (err instanceof JsonError && err.code === JSON_CODES["PARSE.PROTOTYPE_POLLUTION"]) {
console.error("Prototype pollution blocked");
}
}
```
## Options
### `ParseOptions`
```ts
interface ParseOptions {
forbiddenKeys?: string[]; // default: ["__proto__", "constructor", "prototype"]
maxDepth?: number; // default: 50
signal?: AbortSignal;
}
```
### `StringifyOptions`
```ts
interface StringifyOptions {
space?: number;
signal?: AbortSignal;
maxDepth?: number; // default: 100
}
```
## Benchmarks
Measured on Intel Xeon E5-2696 v4 @ 2.20 GHz, Bun 1.3.13:
```text
benchmark avg (min … max)
-------------------------------------------
safeParse small 9.84 µs/iter
JSON.parse small 592.70 ns/iter
safeStringify small 1.99 µs/iter
JSON.stringify small 212.99 ns/iter
safeParse medium 42.11 µs/iter
safeStringify medium 11.92 µs/iter
```
The overhead relative to the native functions comes from the OTel span
creation, reviver/replacer function calls, and ancestor-stack tracking.
In production with a no-op tracer the spans cost ~0 ns.
## Links
- [`src/safe-json/safe-json.md`](src/safe-json/safe-json.md) — implementation notes
- [`src/errors/json-error.md`](src/errors/json-error.md) — error codes
- [`src/types/types.md`](src/types/types.md) — option types