Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

devalue

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

devalue - npm Package Compare versions

Comparing version 3.0.1 to 3.1.0

4

CHANGELOG.md
# devalue changelog
## 3.1.0
- Include `path` in error object if value is unserializable
## 3.0.1

@@ -4,0 +8,0 @@

@@ -25,2 +25,14 @@ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';

class DevalueError extends Error {
/**
* @param {string} message
* @param {string[]} keys
*/
constructor(message, keys) {
super(message);
this.name = 'DevalueError';
this.path = keys.join('');
}
}
/**

@@ -33,6 +45,9 @@ * Turn a value into the JavaScript that creates an equivalent value

/** @type {string[]} */
const keys = [];
/** @param {any} thing */
function walk(thing) {
if (typeof thing === 'function') {
throw new Error(`Cannot stringify a function`);
throw new DevalueError(`Cannot stringify a function`, keys);
}

@@ -60,10 +75,23 @@

case 'Array':
thing.forEach(walk);
/** @type {any[]} */ (thing).forEach((value, i) => {
keys.push(`[${i}]`);
walk(value);
keys.pop();
});
break;
case 'Set':
case 'Map':
Array.from(thing).forEach(walk);
break;
case 'Map':
for (const [key, value] of thing) {
keys.push(
`.get(${is_primitive(key) ? stringify_primitive(key) : '...'})`
);
walk(value);
keys.pop();
}
break;
default:

@@ -78,10 +106,20 @@ const proto = Object.getPrototypeOf(thing);

) {
throw new Error(`Cannot stringify arbitrary non-POJOs`);
throw new DevalueError(
`Cannot stringify arbitrary non-POJOs`,
keys
);
}
if (Object.getOwnPropertySymbols(thing).length > 0) {
throw new Error(`Cannot stringify POJOs with symbolic keys`);
throw new DevalueError(
`Cannot stringify POJOs with symbolic keys`,
keys
);
}
Object.keys(thing).forEach((key) => walk(thing[key]));
for (const key in thing) {
keys.push(`.${key}`);
walk(thing[key]);
keys.pop();
}
}

@@ -88,0 +126,0 @@ }

133

devalue.test.js
import * as vm from 'vm';
import * as assert from 'uvu/assert';
import * as uvu from 'uvu';
import { devalue } from './devalue.js';
let passed = 0;
let failed = 0;
/**

@@ -15,52 +14,14 @@ * @typedef {(name: string, input: any, expected: string) => void} TestFunction

*/
function describe(name, fn) {
console.group(`\n${name}`);
function compare(name, fn) {
const test = uvu.suite(name);
fn((name, input, expected) => {
const actual = devalue(input);
if (actual === expected) {
console.log(`✅ ${name}`);
passed += 1;
} else {
console.log(`❌ ${name}`);
console.log(` actual: ${actual}`);
console.log(` expected: ${expected}`);
failed += 1;
}
test(name, () => {
const actual = devalue(input);
assert.equal(actual, expected);
});
});
console.groupEnd();
test.run();
}
/**
*
* @param {string} name
* @param {(...args: any[]) => void} fn
*/
function throws(name, fn) {
try {
fn();
console.log(`❌ ${name}`);
failed += 1;
} catch (e) {
console.log(`✅ ${name}`);
passed += 1;
}
}
/**
*
* @param {string} name
* @param {(...args: any[]) => void} fn
*/
function allows(name, fn) {
try {
fn();
console.log(`✅ ${name}`);
passed += 1;
} catch (e) {
console.log(`❌ ${name} (${e.message})`);
failed += 1;
}
}
describe('basics', (t) => {
compare('basics', (t) => {
t('number', 42, '42');

@@ -91,3 +52,3 @@ t('negative number', -42, '-42');

describe('strings', (t) => {
compare('strings', (t) => {
t('newline', 'a\nb', JSON.stringify('a\nb'));

@@ -105,3 +66,3 @@ t('double quotes', '"yar"', JSON.stringify('"yar"'));

describe('cycles', (t) => {
compare('cycles', (t) => {
let map = new Map();

@@ -150,3 +111,3 @@ map.set('self', map);

describe('repetition', (t) => {
compare('repetition', (t) => {
let str = 'a string';

@@ -160,3 +121,3 @@ t(

describe('XSS', (t) => {
compare('XSS', (t) => {
t(

@@ -179,34 +140,52 @@ 'Dangerous string',

describe('misc', (t) => {
compare('misc', (t) => {
t('Object without prototype', Object.create(null), 'Object.create(null)');
t('cross-realm POJO', vm.runInNewContext('({})'), '{}');
});
// let arr = [];
// arr.x = 42;
// test('Array with named properties', arr, `TODO`);
uvu.test('throws for non-POJOs', () => {
class Foo {}
const foo = new Foo();
assert.throws(() => devalue(foo));
});
t('cross-realm POJO', vm.runInNewContext('({})'), '{}');
uvu.test('throws for symbolic keys', () => {
assert.throws(() => devalue({ [Symbol()]: null }));
});
throws('throws for non-POJOs', () => {
class Foo {}
const foo = new Foo();
devalue(foo);
});
uvu.test('does not create duplicate parameter names', () => {
const foo = new Array(20000).fill(0).map((_, i) => i);
const bar = foo.map((_, i) => ({ [i]: foo[i] }));
const serialized = devalue([foo, ...bar]);
throws('throws for symbolic keys', () => {
devalue({ [Symbol()]: null });
});
eval(serialized);
});
allows('does not create duplicate parameter names', () => {
const foo = new Array(20000).fill(0).map((_, i) => i);
const bar = foo.map((_, i) => ({ [i]: foo[i] }));
const serialized = devalue([foo, ...bar]);
uvu.test('populates error.keys and error.path', () => {
try {
devalue({
foo: {
array: [function invalid() {}]
}
});
} catch (e) {
assert.equal(e.name, 'DevalueError');
assert.equal(e.message, 'Cannot stringify a function');
assert.equal(e.path, '.foo.array[0]');
}
eval(serialized);
});
try {
class Whatever {}
devalue({
foo: {
map: new Map([['key', new Whatever()]])
}
});
} catch (e) {
assert.equal(e.name, 'DevalueError');
assert.equal(e.message, 'Cannot stringify arbitrary non-POJOs');
assert.equal(e.path, '.foo.map.get("key")');
}
});
console.log(`\n---\n${passed} passed, ${failed} failed\n`);
if (failed > 0) {
process.exit(1);
}
uvu.test.run();
{
"name": "devalue",
"description": "Gets the job done when JSON.stringify can't",
"version": "3.0.1",
"version": "3.1.0",
"repository": "Rich-Harris/devalue",

@@ -13,3 +13,4 @@ "exports": {

"devDependencies": {
"typescript": "^3.1.3"
"typescript": "^3.1.3",
"uvu": "^0.5.6"
},

@@ -16,0 +17,0 @@ "scripts": {

@@ -40,4 +40,21 @@ # devalue

If `devalue` encounters a function or a non-POJO, it will throw an error.
## Error handling
If `devalue` encounters a function or a non-POJO, it will throw an error. You can find where in the input data the offending value lives by inspecting `error.path`:
```js
try {
const map = new Map();
map.set('key', function invalid() {});
devalue({
object: {
array: [map]
}
})
} catch (e) {
console.log(e.path); // '.object.array[0].get("key")'
}
```
## XSS mitigation

@@ -44,0 +61,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc