diff --git a/src/index.ts b/src/index.ts index d1afad2..fec956f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$'; const reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/; const unsafe = /[<>\/\u2028\u2029]/g; const escaped: Record = { '<': '\\u003C', '>' : '\\u003E', '/': '\\u002F', '\u2028': '\\u2028', '\u2029': '\\u2029' }; +const objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0'); export default function devalue(value: any) { const repeated = new Map(); @@ -44,8 +45,16 @@ export default function devalue(value: any) { default: const proto = Object.getPrototypeOf(thing); - if (proto !== Object.prototype && proto !== null) { - throw new Error(`Cannot stringify arbitrary non-POJOs`); + if ( + proto !== Object.prototype && + proto !== null && + Object.getOwnPropertyNames(proto).sort().join('\0') !== objectProtoOwnPropertyNames + ) { + throw new Error(`Cannot stringify arbitrary non-POJOs`); + } + + if (Object.getOwnPropertySymbols(thing).length > 0) { + throw new Error(`Cannot stringify POJOs with symbolic keys`); } Object.keys(thing).forEach(key => walk(thing[key])); diff --git a/test/test.ts b/test/test.ts index de0e2da..4865963 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,4 +1,5 @@ import * as assert from 'assert'; +import * as vm from 'vm'; import devalue from '../src/index'; describe('devalue', () => { @@ -80,5 +81,17 @@ describe('devalue', () => { // let arr = []; // arr.x = 42; // test('Array with named properties', arr, `TODO`); + + test('cross-realm POJO', vm.runInNewContext('({})'), '{}'); + + it('throws for non-POJOs', () => { + class Foo {} + const foo = new Foo(); + assert.throws(() => devalue(foo)); + }); + + it('throws for symbolic keys', () => { + assert.throws(() => devalue({ [Symbol()]: null })); + }); }); }); \ No newline at end of file