Skip to content

Commit 3938d11

Browse files
authored
improve performance of safeParse (#81)
* rename scan to filter * improve performance of safeParse * Fix Linting Issue * add typing test for Array
1 parent 5798ff9 commit 3938d11

File tree

3 files changed

+29
-39
lines changed

3 files changed

+29
-39
lines changed

index.js

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,9 @@ function parse (text, reviver, options) {
1010
if (reviver !== null && typeof reviver === 'object') {
1111
options = reviver
1212
reviver = undefined
13-
} else {
14-
options = {}
1513
}
1614
}
1715

18-
const protoAction = options.protoAction || 'error'
19-
const constructorAction = options.constructorAction || 'error'
20-
2116
if (hasBuffer && Buffer.isBuffer(text)) {
2217
text = text.toString()
2318
}
@@ -30,13 +25,16 @@ function parse (text, reviver, options) {
3025
// Parse normally, allowing exceptions
3126
const obj = JSON.parse(text, reviver)
3227

33-
// options: 'error' (default) / 'remove' / 'ignore'
34-
if (protoAction === 'ignore' && constructorAction === 'ignore') {
28+
// Ignore null and non-objects
29+
if (obj === null || typeof obj !== 'object') {
3530
return obj
3631
}
3732

38-
// Ignore null and non-objects
39-
if (obj === null || typeof obj !== 'object') {
33+
const protoAction = (options && options.protoAction) || 'error'
34+
const constructorAction = (options && options.constructorAction) || 'error'
35+
36+
// options: 'error' (default) / 'remove' / 'ignore'
37+
if (protoAction === 'ignore' && constructorAction === 'ignore') {
4038
return obj
4139
}
4240

@@ -55,12 +53,10 @@ function parse (text, reviver, options) {
5553
}
5654

5755
// Scan result for proto keys
58-
scan(obj, { protoAction, constructorAction })
59-
60-
return obj
56+
return filter(obj, { protoAction, constructorAction, safe: options && options.safe })
6157
}
6258

63-
function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {}) {
59+
function filter (obj, { protoAction = 'error', constructorAction = 'error', safe } = {}) {
6460
let next = [obj]
6561

6662
while (next.length) {
@@ -69,7 +65,9 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {})
6965

7066
for (const node of nodes) {
7167
if (protoAction !== 'ignore' && Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly
72-
if (protoAction === 'error') {
68+
if (safe === true) {
69+
return null
70+
} else if (protoAction === 'error') {
7371
throw new SyntaxError('Object contains forbidden prototype property')
7472
}
7573

@@ -79,7 +77,9 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {})
7977
if (constructorAction !== 'ignore' &&
8078
Object.prototype.hasOwnProperty.call(node, 'constructor') &&
8179
Object.prototype.hasOwnProperty.call(node.constructor, 'prototype')) { // Avoid calling node.hasOwnProperty directly
82-
if (constructorAction === 'error') {
80+
if (safe === true) {
81+
return null
82+
} else if (constructorAction === 'error') {
8383
throw new SyntaxError('Object contains forbidden prototype property')
8484
}
8585

@@ -89,23 +89,24 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {})
8989
for (const key in node) {
9090
const value = node[key]
9191
if (value && typeof value === 'object') {
92-
next.push(node[key])
92+
next.push(value)
9393
}
9494
}
9595
}
9696
}
97+
return obj
9798
}
9899

99100
function safeParse (text, reviver) {
100101
try {
101-
return parse(text, reviver)
102+
return parse(text, reviver, { safe: true })
102103
} catch (ignoreError) {
103104
return null
104105
}
105106
}
106107

107108
module.exports = {
108109
parse,
109-
scan,
110+
scan: filter,
110111
safeParse
111112
}

types/index.d.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,17 @@ export type ParseOptions = {
55
* - `'remove'` - deletes any `__proto__` keys from the result object.
66
* - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
77
*/
8-
protoAction?: 'error' | 'remove' | 'ignore',
8+
protoAction?: 'error' | 'remove' | 'ignore';
99
/**
1010
* What to do when a `constructor` key is found.
1111
* - `'error'` - throw a `SyntaxError` when a `constructor.prototype` key is found. This is the default value.
1212
* - `'remove'` - deletes any `constructor` keys from the result object.
1313
* - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
1414
*/
15-
constructorAction?: 'error' | 'remove' | 'ignore',
15+
constructorAction?: 'error' | 'remove' | 'ignore';
1616
}
1717

18-
export type ScanOptions = {
19-
/**
20-
* What to do when a `__proto__` key is found.
21-
* - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value.
22-
* - `'remove'` - deletes any `__proto__` keys from the input `obj`.
23-
*/
24-
protoAction?: 'error' | 'remove',
25-
/**
26-
* What to do when a `constructor` key is found.
27-
* - `'error'` - throw a `SyntaxError` when a `constructor.prototype` key is found. This is the default value.
28-
* - `'remove'` - deletes any `constructor` keys from the input `obj`.
29-
*/
30-
constructorAction?: 'error' | 'remove',
31-
}
18+
export type ScanOptions = ParseOptions
3219

3320
type Reviver = (this: any, key: string, value: any) => any
3421

@@ -56,5 +43,6 @@ export function safeParse(text: string | Buffer, reviver?: Reviver | null): any
5643
*
5744
* @param obj The object being scanned.
5845
* @param options Optional configuration object.
46+
* @returns The object, or `null` if onError is set to `nullify`
5947
*/
60-
export function scan(obj: any, options?: ScanOptions): void
48+
export function scan(obj: {[key: string | number]: any }, options?: ParseOptions): any

types/index.test-d.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ sjson.safeParse('"test"', null)
1313
sjson.safeParse('"test"')
1414
expectError(sjson.safeParse(null))
1515

16-
sjson.scan('"test"', { protoAction: 'remove' })
17-
expectError(sjson.scan('"test"', { protoAction: 'ignore' }))
18-
sjson.scan('"test"', { constructorAction: 'error' })
19-
expectError(sjson.scan('"test"', { constructorAction: 'ignore' }))
16+
sjson.scan({}, { protoAction: 'remove' })
17+
sjson.scan({}, { protoAction: 'ignore' })
18+
sjson.scan({}, { constructorAction: 'error' })
19+
sjson.scan({}, { constructorAction: 'ignore' })
20+
sjson.scan(new Array(), {})
2021

2122
declare const input: Buffer
2223
sjson.parse(input)

0 commit comments

Comments
 (0)