Skip to content

Commit

Permalink
feat!: support generic type with unkown default (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Jun 12, 2023
1 parent a73b0ff commit 1d01ba1
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 25 deletions.
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,63 +23,72 @@ Import into your Node.js project:

```js
// CommonJS
const destr = require('destr')
const destr = require("destr");

// ESM
import destr from 'destr'
import destr from "destr";
```

### Deno

```js
import destr from 'https://deno.land/x/destr/src/index.ts'
import destr from "https://deno.land/x/destr/src/index.ts";

console.log(destr('{ "deno": "yay" }'))
console.log(destr('{ "deno": "yay" }'));
```


## Why?

**Type safe:**

```js
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:**

```js
// Uncaught SyntaxError: Unexpected token u in JSON at position 0
JSON.parse()
JSON.parse();

// undefined
destr()
destr();
```

**Fast lookup for known string values:**

```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):**

```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:**

```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
Expand All @@ -88,10 +97,10 @@ If `{ strict: true }` passed as second argument, `destr` will throw an error if

```js
// Returns "[foo"
destr('[foo')
destr("[foo");

// Throws an error
destr('[foo', { strict: true })
destr("[foo", { strict: true });
```

## Benchmarks
Expand Down Expand Up @@ -147,6 +156,7 @@ Fastest is JSON.parse (try-catch)
MIT. Made with 💖

<!-- Badges -->

[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
Expand Down
21 changes: 12 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,40 @@ export type Options = {
strict?: boolean;
};

export default function destr(value: any, options: Options = {}): any {
export default function destr<T = unknown>(
value: any,
options: Options = {}
): T {
if (typeof value !== "string") {
return value;
}

const _lval = value.toLowerCase().trim();
if (_lval === "true") {
return true;
return true as T;
}
if (_lval === "false") {
return false;
return false as T;
}
if (_lval === "null") {
// eslint-disable-next-line unicorn/no-null
return null;
return null as T;
}
if (_lval === "nan") {
return Number.NaN;
return Number.NaN as T;
}
if (_lval === "infinity") {
return Number.POSITIVE_INFINITY;
return Number.POSITIVE_INFINITY as T;
}
if (_lval === "undefined") {
return undefined;
return undefined as T;
}

if (!JsonSigRx.test(value)) {
if (options.strict) {
throw new SyntaxError("Invalid JSON");
}
return value;
return value as T;
}

try {
Expand All @@ -69,6 +72,6 @@ export default function destr(value: any, options: Options = {}): any {
if (options.strict) {
throw error;
}
return value;
return value as T;
}
}

0 comments on commit 1d01ba1

Please sign in to comment.