Skip to content

Commit 16bc8c2

Browse files
authored
Merge pull request #48 from Rich-Harris/unflatten
Unflatten
2 parents e39f9e4 + a506ffe commit 16bc8c2

File tree

4 files changed

+61
-23
lines changed

4 files changed

+61
-23
lines changed

README.md

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,37 @@ devalue.parse(stringified); // { message: 'hello', self: [Circular] }
6363

6464
Use `stringify` and `parse` when evaluating JavaScript isn't an option.
6565

66+
### `unflatten`
67+
68+
In the case where devalued data is one part of a larger JSON string, `unflatten` allows you to revive just the bit you need:
69+
70+
```js
71+
import * as devalue from 'devalue';
72+
73+
const json = `{
74+
"type": "data",
75+
"data": ${devalue.stringify(data)}
76+
}`;
77+
78+
const data = devalue.unflatten(JSON.parse(json).data);
79+
```
80+
6681
## Error handling
6782

6883
If `uneval` or `stringify` 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`:
6984

7085
```js
7186
try {
72-
const map = new Map();
73-
map.set('key', function invalid() {});
74-
75-
uneval({
76-
object: {
77-
array: [map]
78-
}
79-
});
87+
const map = new Map();
88+
map.set('key', function invalid() {});
89+
90+
uneval({
91+
object: {
92+
array: [map]
93+
}
94+
});
8095
} catch (e) {
81-
console.log(e.path); // '.object.array[0].get("key")'
96+
console.log(e.path); // '.object.array[0].get("key")'
8297
}
8398
```
8499

@@ -88,7 +103,7 @@ Say you're server-rendering a page and want to serialize some state, which could
88103

89104
```js
90105
const state = {
91-
userinput: `</script><script src='https://evil.com/mwahaha.js'>`
106+
userinput: `</script><script src='https://evil.com/mwahaha.js'>`
92107
};
93108

94109
const template = `
@@ -102,11 +117,11 @@ Which would result in this:
102117

103118
```html
104119
<script>
105-
// NEVER DO THIS
106-
var preloaded = {"userinput":"
120+
// NEVER DO THIS
121+
var preloaded = {"userinput":"
107122
</script>
108123
<script src="https://evil.com/mwahaha.js">
109-
"};
124+
"};
110125
</script>
111126
```
112127
@@ -121,10 +136,10 @@ const template = `
121136
122137
```html
123138
<script>
124-
var preloaded = {
125-
userinput:
126-
"\\u003C\\u002Fscript\\u003E\\u003Cscript src='https:\\u002F\\u002Fevil.com\\u002Fmwahaha.js'\\u003E"
127-
};
139+
var preloaded = {
140+
userinput:
141+
"\\u003C\\u002Fscript\\u003E\\u003Cscript src='https:\\u002F\\u002Fevil.com\\u002Fmwahaha.js'\\u003E"
142+
};
128143
</script>
129144
```
130145
@@ -142,9 +157,9 @@ When using `eval`, ensure that you call it _indirectly_ so that the evaluated co
142157
143158
```js
144159
{
145-
const sensitiveData = 'Setec Astronomy';
146-
eval('sendToEvilServer(sensitiveData)'); // pwned :(
147-
(0, eval)('sendToEvilServer(sensitiveData)'); // nice try, evildoer!
160+
const sensitiveData = 'Setec Astronomy';
161+
eval('sendToEvilServer(sensitiveData)'); // pwned :(
162+
(0, eval)('sendToEvilServer(sensitiveData)'); // nice try, evildoer!
148163
}
149164
```
150165

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { uneval } from './src/uneval.js';
2-
export { parse } from './src/parse.js';
2+
export { parse, unflatten } from './src/parse.js';
33
export { stringify } from './src/stringify.js';

src/parse.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import {
1212
* @param {string} serialized
1313
*/
1414
export function parse(serialized) {
15-
const parsed = JSON.parse(serialized);
15+
return unflatten(JSON.parse(serialized));
16+
}
1617

18+
/**
19+
* Revive a value flattened with `devalue.flatten`
20+
* @param {number | any[]} parsed
21+
*/
22+
export function unflatten(parsed) {
1723
if (typeof parsed === 'number') return hydrate(parsed, true);
1824

1925
if (!Array.isArray(parsed) || parsed.length === 0) {

test/test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as vm from 'vm';
22
import * as assert from 'uvu/assert';
33
import * as uvu from 'uvu';
4-
import { uneval, parse, stringify } from '../index.js';
4+
import { uneval, unflatten, parse, stringify } from '../index.js';
55

66
const fixtures = {
77
basics: [
@@ -406,6 +406,23 @@ for (const [name, tests] of Object.entries(fixtures)) {
406406
test.run();
407407
}
408408

409+
for (const [name, tests] of Object.entries(fixtures)) {
410+
const test = uvu.suite(`unflatten: ${name}`);
411+
for (const t of tests) {
412+
test(t.name, () => {
413+
const actual = unflatten(JSON.parse(t.json));
414+
const expected = t.value;
415+
416+
if (t.validate) {
417+
t.validate(actual);
418+
} else {
419+
assert.equal(actual, expected);
420+
}
421+
});
422+
}
423+
test.run();
424+
}
425+
409426
const invalid = [
410427
{
411428
name: 'empty string',

0 commit comments

Comments
 (0)