Comparing version
type Options = { | ||
strict?: boolean; | ||
}; | ||
declare function destr(value: any, options?: Options): any; | ||
declare function destr<T = unknown>(value: any, options?: Options): T; | ||
declare function safeDestr<T = unknown>(value: any, options?: Options): T; | ||
export { Options, destr as default }; | ||
export { Options, destr as default, destr, safeDestr }; |
{ | ||
"name": "destr", | ||
"version": "1.2.2", | ||
"version": "2.0.0", | ||
"description": "A faster, secure and convenient alternative for JSON.parse", | ||
@@ -12,32 +12,35 @@ "repository": "unjs/destr", | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.cjs" | ||
"require": "./lib/index.cjs" | ||
} | ||
}, | ||
"main": "./dist/index.cjs", | ||
"main": "./lib/index.cjs", | ||
"module": "./dist/index.mjs", | ||
"types": "./dist/index.d.ts", | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"lib" | ||
], | ||
"scripts": { | ||
"bench": "pnpm build && node ./bench.mjs", | ||
"build": "unbuild", | ||
"dev": "vitest dev", | ||
"lint": "eslint --ext .ts . && prettier -c src test", | ||
"lint:fix": "eslint --ext .ts . --fix && prettier -w src test", | ||
"release": "pnpm test && pnpm build && changelogen --release --push && npm publish", | ||
"test": "pnpm lint && vitest run --coverage" | ||
}, | ||
"devDependencies": { | ||
"@hapi/bourne": "^3.0.0", | ||
"@vitest/coverage-c8": "^0.25.3", | ||
"@vitest/coverage-v8": "^0.32.0", | ||
"benchmark": "^2.1.4", | ||
"eslint": "^8.29.0", | ||
"eslint-config-unjs": "^0.0.2", | ||
"secure-json-parse": "^2.6.0", | ||
"standard-version": "^9.5.0", | ||
"typescript": "^4.9.3", | ||
"unbuild": "^1.0.1", | ||
"vitest": "^0.25.3" | ||
"changelogen": "^0.5.3", | ||
"eslint": "^8.42.0", | ||
"eslint-config-unjs": "^0.2.1", | ||
"prettier": "^2.8.8", | ||
"secure-json-parse": "^2.7.0", | ||
"typescript": "^5.1.3", | ||
"unbuild": "^1.2.1", | ||
"vitest": "^0.32.0" | ||
}, | ||
"packageManager": "pnpm@7.18.0", | ||
"scripts": { | ||
"bench": "pnpm build && node ./bench.cjs", | ||
"build": "unbuild", | ||
"dev": "vitest dev", | ||
"lint": "eslint --ext .ts .", | ||
"release": "pnpm test && pnpm build && standard-version && git push --follow-tags && pnpm publish", | ||
"test": "pnpm lint && vitest run --coverage" | ||
} | ||
"packageManager": "pnpm@8.6.2" | ||
} |
161
README.md
# destr | ||
> A faster, secure and convenient alternative for [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse): | ||
[![npm version][npm-version-src]][npm-version-href] | ||
[![npm downloads][npm-downloads-src]][npm-downloads-href] | ||
[![bundle][bundle-src]][bundle-href] | ||
[![License][license-src]][license-href] | ||
[![npm version][npm-v-src]][npm-v-href] | ||
[![npm downloads][npm-d-src]][npm-d-href] | ||
[![bundle phobia][bundlephobia-src]][bundlephobia-href] | ||
A faster, secure and convenient alternative for [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). | ||
@@ -13,8 +14,13 @@ ## Usage | ||
Install using npm or yarn: | ||
Install dependency: | ||
```bash | ||
# npm | ||
npm i destr | ||
# or | ||
# yarn | ||
yarn add destr | ||
# pnpm | ||
pnpm i destr | ||
``` | ||
@@ -25,7 +31,7 @@ | ||
```js | ||
// ESM | ||
import { destr, safeDestr } from "destr"; | ||
// CommonJS | ||
const destr = require('destr') | ||
// ESM | ||
import destr from 'destr' | ||
const { destr, safeDestr } = require("destr"); | ||
``` | ||
@@ -36,62 +42,79 @@ | ||
```js | ||
import destr from 'https://deno.land/x/destr/src/index.ts' | ||
import { destr, safeDestr } from "https://deno.land/x/destr/src/index.ts"; | ||
console.log(destr('{ "deno": "yay" }')) | ||
console.log(destr('{ "deno": "yay" }')); | ||
``` | ||
## Why? | ||
**Fast fallback to input if is not string:** | ||
### ✅ Type Safe | ||
```ts | ||
const obj = JSON.parse("{}"); // obj type is any | ||
const obj = destr("{}"); // obj type is unknown by default | ||
const obj = destr<MyInterface>("{}"); // obj is well-typed | ||
``` | ||
### ✅ Fast fallback to input if is not string | ||
> 🚀 Up to 500 faster than `JSON.parse`! | ||
```js | ||
// Uncaught SyntaxError: Unexpected token u in JSON at position 0 | ||
JSON.parse() | ||
JSON.parse(); | ||
// undefined | ||
destr() | ||
destr(); | ||
``` | ||
**Fast lookup for known string values:** | ||
### ✅ Fast lookup for known string values | ||
> 🚀 Up to 900 times faster than `JSON.parse`! | ||
```js | ||
// Uncaught SyntaxError: Unexpected token T in JSON at position 0 | ||
JSON.parse('TRUE') | ||
JSON.parse("TRUE"); | ||
// true | ||
destr('TRUE') | ||
destr("TRUE"); | ||
``` | ||
**Fallback to original value if parse fails (empty or any plain string):** | ||
### ✅ Fallback to original value if parse fails (empty or any plain string) | ||
> 🚀 Up to 900 times faster than `JSON.parse`! | ||
```js | ||
// Uncaught SyntaxError: Unexpected token s in JSON at position 0 | ||
JSON.parse('salam') | ||
JSON.parse("salam"); | ||
// "salam" | ||
destr('salam') | ||
destr("salam"); | ||
``` | ||
**Avoid prototype pollution:** | ||
**Note:** This fails in safe/strict mode with `safeDestr`. | ||
### ✅ Avoid prototype pollution | ||
```js | ||
const input = '{ "user": { "__proto__": { "isAdmin": true } } }' | ||
const input = '{ "user": { "__proto__": { "isAdmin": true } } }'; | ||
// { user: { __proto__: { isAdmin: true } } } | ||
JSON.parse(input) | ||
JSON.parse(input); | ||
// { user: {} } | ||
destr(input) | ||
destr(input); | ||
``` | ||
### Strict Mode | ||
### ✅ Strict Mode | ||
If `{ strict: true }` passed as second argument, `destr` will throw an error if the input is not a valid JSON string or parsing fails. (non string values and built-ins will be still returned as-is) | ||
When using `safeDestr` it will throw an error if the input is not a valid JSON string or parsing fails. (non string values and built-ins will be still returned as-is) | ||
```js | ||
// Returns "[foo" | ||
destr('[foo') | ||
safeDestr("[foo"); | ||
// Throws an error | ||
destr('[foo', { strict: true }) | ||
safeDestr("[foo", { strict: true }); | ||
``` | ||
@@ -101,3 +124,3 @@ | ||
Locally try with `pnpm benchmark`. Below are esults on Node.js 18.11.0 with MBA M2. | ||
Locally try with `pnpm benchmark`. Below are esults on Node.js **v18.16.0** with MBA M2. | ||
@@ -108,39 +131,39 @@ **Note** `destr` is sometimes little bit slower than `JSON.parse` when parsing a valid JSON string mainly because of transform to avoid [prototype pollution](https://learn.snyk.io/lessons/prototype-pollution/javascript/) which can lead to serious security issues if not being sanitized. In the other words, `destr` is better when input is not always a json string or from untrusted source like request body. | ||
=== Non-string fallback == | ||
JSON.parse x 10,323,718 ops/sec ±0.45% (96 runs sampled) | ||
destr x 1,057,268,114 ops/sec ±1.71% (90 runs sampled) | ||
destr (strict) x 977,215,995 ops/sec ±1.43% (97 runs sampled) | ||
JSON.parse x 9,498,532 ops/sec ±0.57% (96 runs sampled) | ||
destr x 153,323,211 ops/sec ±0.13% (99 runs sampled) | ||
safeDestr x 64,237,062 ops/sec ±0.22% (96 runs sampled) | ||
sjson: | ||
@hapi/bourne x 10,151,985 ops/sec ±0.76% (96 runs sampled) | ||
@hapi/bourne x 9,190,459 ops/sec ±0.50% (93 runs sampled) | ||
Fastest is destr | ||
=== Known values == | ||
JSON.parse x 16,359,358 ops/sec ±0.90% (92 runs sampled) | ||
destr x 107,849,085 ops/sec ±0.34% (97 runs sampled) | ||
destr (strict) x 107,891,427 ops/sec ±0.34% (99 runs sampled) | ||
sjson x 14,216,957 ops/sec ±0.98% (89 runs sampled) | ||
@hapi/bourne x 15,209,152 ops/sec ±1.08% (88 runs sampled) | ||
Fastest is destr (strict),destr | ||
JSON.parse x 14,260,909 ops/sec ±0.54% (95 runs sampled) | ||
destr x 72,916,945 ops/sec ±0.15% (98 runs sampled) | ||
safeDestr x 36,544,906 ops/sec ±0.31% (98 runs sampled) | ||
sjson x 11,157,730 ops/sec ±0.53% (96 runs sampled) | ||
@hapi/bourne x 13,241,853 ops/sec ±0.73% (93 runs sampled) | ||
Fastest is destr | ||
=== Plain string == | ||
JSON.parse (try-catch) x 211,560 ops/sec ±0.84% (92 runs sampled) | ||
destr x 60,315,113 ops/sec ±0.46% (98 runs sampled) | ||
destr (strict): | ||
sjson (try-catch) x 186,492 ops/sec ±0.70% (97 runs sampled) | ||
@hapi/bourne: | ||
=== plain string == | ||
JSON.parse (try-catch) x 10,603,912 ops/sec ±0.75% (91 runs sampled) | ||
destr x 82,123,481 ops/sec ±2.37% (99 runs sampled) | ||
safeDestr x 40,737,935 ops/sec ±0.97% (96 runs sampled) | ||
sjson (try-catch) x 9,194,305 ops/sec ±1.96% (94 runs sampled) | ||
@hapi/bourne x 10,816,232 ops/sec ±1.59% (90 runs sampled) | ||
Fastest is destr | ||
=== standard object == | ||
JSON.parse x 492,180 ops/sec ±0.98% (98 runs sampled) | ||
destr x 356,819 ops/sec ±0.40% (98 runs sampled) | ||
destr (strict) x 412,955 ops/sec ±0.88% (94 runs sampled) | ||
sjson x 437,376 ops/sec ±0.42% (102 runs sampled) | ||
@hapi/bourne x 457,020 ops/sec ±0.81% (99 runs sampled) | ||
=== package.json == | ||
JSON.parse x 403,428 ops/sec ±0.31% (101 runs sampled) | ||
destr x 338,668 ops/sec ±0.27% (97 runs sampled) | ||
safeDestr x 335,756 ops/sec ±0.29% (98 runs sampled) | ||
sjson x 355,493 ops/sec ±0.15% (101 runs sampled) | ||
@hapi/bourne x 384,948 ops/sec ±0.24% (98 runs sampled) | ||
Fastest is JSON.parse | ||
=== invalid syntax == | ||
JSON.parse (try-catch) x 493,739 ops/sec ±0.51% (98 runs sampled) | ||
destr x 405,848 ops/sec ±0.56% (100 runs sampled) | ||
destr (strict) x 409,514 ops/sec ±0.57% (101 runs sampled) | ||
sjson (try-catch) x 435,406 ops/sec ±0.41% (100 runs sampled) | ||
@hapi/bourne x 467,163 ops/sec ±0.42% (99 runs sampled) | ||
=== broken object == | ||
JSON.parse (try-catch) x 406,262 ops/sec ±0.18% (100 runs sampled) | ||
destr x 337,602 ops/sec ±0.37% (99 runs sampled) | ||
safeDestr x 320,071 ops/sec ±0.35% (97 runs sampled) | ||
sjson (try-catch) x 326,689 ops/sec ±0.41% (97 runs sampled) | ||
@hapi/bourne x 313,024 ops/sec ±0.91% (94 runs sampled) | ||
Fastest is JSON.parse (try-catch) | ||
@@ -153,13 +176,11 @@ ``` | ||
<!-- Refs --> | ||
[npm-v-src]: https://img.shields.io/npm/v/destr?style=flat-square | ||
[npm-v-href]: https://npmjs.com/package/destr | ||
<!-- Badges --> | ||
[npm-d-src]: https://img.shields.io/npm/dm/destr?style=flat-square | ||
[npm-d-href]: https://npmjs.com/package/destr | ||
[github-actions-src]: https://img.shields.io/github/workflow/status/unjs/destr/ci/master?style=flat-square | ||
[github-actions-href]: https://github.com/unjs/destr/actions?query=workflow%3Aci | ||
[bundlephobia-src]: https://img.shields.io/bundlephobia/min/destr?style=flat-square | ||
[bundlephobia-href]: https://bundlephobia.com/result?p=destr | ||
[npm-version-src]: https://img.shields.io/npm/v/destr?style=flat&colorA=18181B&colorB=F0DB4F | ||
[npm-version-href]: https://npmjs.com/package/destr | ||
[npm-downloads-src]: https://img.shields.io/npm/dm/destr?style=flat&colorA=18181B&colorB=F0DB4F | ||
[npm-downloads-href]: https://npmjs.com/package/destr | ||
[bundle-src]: https://img.shields.io/bundlephobia/minzip/destr?style=flat&colorA=18181B&colorB=F0DB4F | ||
[bundle-href]: https://bundlephobia.com/result?p=destr | ||
[license-src]: https://img.shields.io/github/license/unjs/destr.svg?style=flat&colorA=18181B&colorB=F0DB4F | ||
[license-href]: https://github.com/unjs/destr/blob/main/LICENSE |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
11925
22.58%7
16.67%151
32.46%181
13.13%11
10%