Skip to content

Commit a5d6837

Browse files
pimterryokuryu
authored andcommitted
Add support for serializing ES6 sets & maps (#45)
1 parent 35f6480 commit a5d6837

File tree

3 files changed

+71
-6
lines changed

3 files changed

+71
-6
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ Serialize JavaScript to a _superset_ of JSON that includes regular expressions,
1111

1212
The code in this package began its life as an internal module to [express-state][]. To expand its usefulness, it now lives as `serialize-javascript` — an independent package on npm.
1313

14-
You're probably wondering: **What about `JSON.stringify()`!?** We've found that sometimes we need to serialize JavaScript **functions**, **regexps** or **dates**. A great example is a web app that uses client-side URL routing where the route definitions are regexps that need to be shared from the server to the client. But this module is also great for communicating between node processes.
14+
You're probably wondering: **What about `JSON.stringify()`!?** We've found that sometimes we need to serialize JavaScript **functions**, **regexps**, **dates**, **sets** or **maps**. A great example is a web app that uses client-side URL routing where the route definitions are regexps that need to be shared from the server to the client. But this module is also great for communicating between node processes.
1515

1616
The string returned from this package's single export function is literal JavaScript which can be saved to a `.js` file, or be embedded into an HTML document by making the content of a `<script>` element.
1717

1818
> **HTML characters and JavaScript line terminators are escaped automatically.**
1919
20+
Please note that serialization for ES6 Sets & Maps requires support for `Array.from` (not available in IE or Node < 0.12), or an `Array.from` polyfill.
2021

2122
## Installation
2223

@@ -40,6 +41,8 @@ serialize({
4041
nil : null,
4142
undef: undefined,
4243
date: new Date("Thu, 28 Apr 2016 22:02:17 GMT"),
44+
map: new Map([['hello', 'world']]),
45+
set: new Set([123, 456]),
4346

4447
fn: function echo(arg) { return arg; },
4548
re: /([^\s]+)/g
@@ -49,7 +52,7 @@ serialize({
4952
The above will produce the following string output:
5053

5154
```js
52-
'{"str":"string","num":0,"obj":{"foo":"foo"},"arr":[1,2,3],"bool":true,"nil":null,date:new Date("2016-04-28T22:02:17.156Z"),"fn":function echo(arg) { return arg; },"re":/([^\\s]+)/g}'
55+
'{"str":"string","num":0,"obj":{"foo":"foo"},"arr":[1,2,3],"bool":true,"nil":null,date:new Date("2016-04-28T22:02:17.156Z"),new Map([["hello", "world"]]),new Set([123,456]),"fn":function echo(arg) { return arg; },"re":/([^\\s]+)/g}'
5356
```
5457

5558
Note: to produced a beautified string, you can pass an optional second argument to `serialize()` to define the number of spaces to be used for the indentation.

index.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ See the accompanying LICENSE file for terms.
88

99
// Generate an internal UID to make the regexp pattern harder to guess.
1010
var UID = Math.floor(Math.random() * 0x10000000000).toString(16);
11-
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D)-' + UID + '-(\\d+)__@"', 'g');
11+
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D|M|S)-' + UID + '-(\\d+)__@"', 'g');
1212

1313
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
1414
var IS_PURE_FUNCTION = /function.*?\(/;
@@ -41,6 +41,8 @@ module.exports = function serialize(obj, options) {
4141
var functions = [];
4242
var regexps = [];
4343
var dates = [];
44+
var maps = [];
45+
var sets = [];
4446

4547
// Returns placeholders for functions and regexps (identified by index)
4648
// which are later replaced by their string representation.
@@ -62,6 +64,14 @@ module.exports = function serialize(obj, options) {
6264
if(origValue instanceof Date) {
6365
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
6466
}
67+
68+
if(origValue instanceof Map) {
69+
return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@';
70+
}
71+
72+
if(origValue instanceof Set) {
73+
return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@';
74+
}
6575
}
6676

6777
if (type === 'function') {
@@ -126,11 +136,11 @@ module.exports = function serialize(obj, options) {
126136
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
127137
}
128138

129-
if (functions.length === 0 && regexps.length === 0 && dates.length === 0) {
139+
if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0) {
130140
return str;
131141
}
132142

133-
// Replaces all occurrences of function, regexp and date placeholders in the
143+
// Replaces all occurrences of function, regexp, date, map and set placeholders in the
134144
// JSON string with their string representations. If the original value can
135145
// not be found, then `undefined` is used.
136146
return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) {
@@ -142,7 +152,15 @@ module.exports = function serialize(obj, options) {
142152
return regexps[valueIndex].toString();
143153
}
144154

145-
var fn = functions[valueIndex];
155+
if (type === 'M') {
156+
return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")";
157+
}
158+
159+
if (type === 'S') {
160+
return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")";
161+
}
162+
163+
var fn = functions[valueIndex];
146164

147165
return serializeFunc(fn);
148166
});

test/unit/serialize.js

+44
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,50 @@ describe('serialize( obj )', function () {
198198
});
199199
});
200200

201+
describe('maps', function () {
202+
it('should serialize maps', function () {
203+
var regexKey = /.*/;
204+
var m = new Map([
205+
['a', 123],
206+
[regexKey, 456]
207+
]);
208+
expect(serialize(m)).to.be.a('string').equal('new Map([["a",123],[/.*/,456]])');
209+
expect(serialize({t: [m]})).to.be.a('string').equal('{"t":[new Map([["a",123],[/.*/,456]])]}');
210+
});
211+
212+
it('should deserialize a map', function () {
213+
var m = eval(serialize(new Map([
214+
['a', 123],
215+
[null, 456]
216+
])));
217+
expect(m).to.be.a('Map');
218+
expect(m.get(null)).to.equal(456);
219+
});
220+
});
221+
222+
describe('sets', function () {
223+
it('should serialize sets', function () {
224+
var regex = /.*/;
225+
var m = new Set([
226+
'a',
227+
123,
228+
regex
229+
]);
230+
expect(serialize(m)).to.be.a('string').equal('new Set(["a",123,/.*/])');
231+
expect(serialize({t: [m]})).to.be.a('string').equal('{"t":[new Set(["a",123,/.*/])]}');
232+
});
233+
234+
it('should deserialize a set', function () {
235+
var m = eval(serialize(new Set([
236+
'a',
237+
123,
238+
null
239+
])));
240+
expect(m).to.be.a('Set');
241+
expect(m.has(null)).to.equal(true);
242+
});
243+
});
244+
201245
describe('XSS', function () {
202246
it('should encode unsafe HTML chars to Unicode', function () {
203247
expect(serialize('</script>')).to.equal('"\\u003C\\u002Fscript\\u003E"');

0 commit comments

Comments
 (0)