Skip to content

Commit

Permalink
improve performance of safeParse (#81)
Browse files Browse the repository at this point in the history
* rename scan to filter

* improve performance of safeParse

* Fix Linting Issue

* add typing test for Array
  • Loading branch information
Uzlopak authored Jul 29, 2022
1 parent 5798ff9 commit 3938d11
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 39 deletions.
37 changes: 19 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,9 @@ function parse (text, reviver, options) {
if (reviver !== null && typeof reviver === 'object') {
options = reviver
reviver = undefined
} else {
options = {}
}
}

const protoAction = options.protoAction || 'error'
const constructorAction = options.constructorAction || 'error'

if (hasBuffer && Buffer.isBuffer(text)) {
text = text.toString()
}
Expand All @@ -30,13 +25,16 @@ function parse (text, reviver, options) {
// Parse normally, allowing exceptions
const obj = JSON.parse(text, reviver)

// options: 'error' (default) / 'remove' / 'ignore'
if (protoAction === 'ignore' && constructorAction === 'ignore') {
// Ignore null and non-objects
if (obj === null || typeof obj !== 'object') {
return obj
}

// Ignore null and non-objects
if (obj === null || typeof obj !== 'object') {
const protoAction = (options && options.protoAction) || 'error'
const constructorAction = (options && options.constructorAction) || 'error'

// options: 'error' (default) / 'remove' / 'ignore'
if (protoAction === 'ignore' && constructorAction === 'ignore') {
return obj
}

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

// Scan result for proto keys
scan(obj, { protoAction, constructorAction })

return obj
return filter(obj, { protoAction, constructorAction, safe: options && options.safe })
}

function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {}) {
function filter (obj, { protoAction = 'error', constructorAction = 'error', safe } = {}) {
let next = [obj]

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

for (const node of nodes) {
if (protoAction !== 'ignore' && Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly
if (protoAction === 'error') {
if (safe === true) {
return null
} else if (protoAction === 'error') {
throw new SyntaxError('Object contains forbidden prototype property')
}

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

Expand All @@ -89,23 +89,24 @@ function scan (obj, { protoAction = 'error', constructorAction = 'error' } = {})
for (const key in node) {
const value = node[key]
if (value && typeof value === 'object') {
next.push(node[key])
next.push(value)
}
}
}
}
return obj
}

function safeParse (text, reviver) {
try {
return parse(text, reviver)
return parse(text, reviver, { safe: true })
} catch (ignoreError) {
return null
}
}

module.exports = {
parse,
scan,
scan: filter,
safeParse
}
22 changes: 5 additions & 17 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,17 @@ export type ParseOptions = {
* - `'remove'` - deletes any `__proto__` keys from the result object.
* - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
*/
protoAction?: 'error' | 'remove' | 'ignore',
protoAction?: 'error' | 'remove' | 'ignore';
/**
* What to do when a `constructor` key is found.
* - `'error'` - throw a `SyntaxError` when a `constructor.prototype` key is found. This is the default value.
* - `'remove'` - deletes any `constructor` keys from the result object.
* - `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
*/
constructorAction?: 'error' | 'remove' | 'ignore',
constructorAction?: 'error' | 'remove' | 'ignore';
}

export type ScanOptions = {
/**
* What to do when a `__proto__` key is found.
* - `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value.
* - `'remove'` - deletes any `__proto__` keys from the input `obj`.
*/
protoAction?: 'error' | 'remove',
/**
* What to do when a `constructor` key is found.
* - `'error'` - throw a `SyntaxError` when a `constructor.prototype` key is found. This is the default value.
* - `'remove'` - deletes any `constructor` keys from the input `obj`.
*/
constructorAction?: 'error' | 'remove',
}
export type ScanOptions = ParseOptions

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

Expand Down Expand Up @@ -56,5 +43,6 @@ export function safeParse(text: string | Buffer, reviver?: Reviver | null): any
*
* @param obj The object being scanned.
* @param options Optional configuration object.
* @returns The object, or `null` if onError is set to `nullify`
*/
export function scan(obj: any, options?: ScanOptions): void
export function scan(obj: {[key: string | number]: any }, options?: ParseOptions): any
9 changes: 5 additions & 4 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ sjson.safeParse('"test"', null)
sjson.safeParse('"test"')
expectError(sjson.safeParse(null))

sjson.scan('"test"', { protoAction: 'remove' })
expectError(sjson.scan('"test"', { protoAction: 'ignore' }))
sjson.scan('"test"', { constructorAction: 'error' })
expectError(sjson.scan('"test"', { constructorAction: 'ignore' }))
sjson.scan({}, { protoAction: 'remove' })
sjson.scan({}, { protoAction: 'ignore' })
sjson.scan({}, { constructorAction: 'error' })
sjson.scan({}, { constructorAction: 'ignore' })
sjson.scan(new Array(), {})

declare const input: Buffer
sjson.parse(input)
Expand Down

0 comments on commit 3938d11

Please sign in to comment.