124 lines
5.5 KiB
Markdown
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
|
|
|