Skip to content

Commit ba11f28

Browse files
committed
Support dates
We detect dates simply by doing an instanceof check on raw value being serialized. We're also now looking at the typeof the *rawValue* not the toJSON()ed value when determining whether a value is a function or a RegExp. This should allow functions and regexs to be serialized properly even if a Function.prototype.toJSON or a RegExp.prototype.toJSON has been added for some reason. Finally, for consistency and because util.isRegExp has been deprecated, we also update our logic for detecting RegExps to use the same strategy as for Dates.
1 parent adfee60 commit ba11f28

File tree

3 files changed

+44
-15
lines changed

3 files changed

+44
-15
lines changed

README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Serialize JavaScript
22
====================
33

4-
Serialize JavaScript to a _superset_ of JSON that includes regular expressions and functions.
4+
Serialize JavaScript to a _superset_ of JSON that includes regular expressions, dates and functions.
55

66
[![npm Version][npm-badge]][npm]
77
[![Dependency Status][david-badge]][david]
@@ -11,7 +11,7 @@ Serialize JavaScript to a _superset_ of JSON that includes regular expressions a
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** and **regexps**. 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.
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.
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. **HTML charaters and JavaScript line terminators are escaped automatically.**
1717

@@ -36,6 +36,7 @@ serialize({
3636
bool : true,
3737
nil : null,
3838
undef: undefined,
39+
date: new Date("Thu, 28 Apr 2016 22:02:17 GMT"),
3940

4041
fn: function echo(arg) { return arg; },
4142
re: /([^\s]+)/g
@@ -45,7 +46,7 @@ serialize({
4546
The above will produce the following string output:
4647

4748
```js
48-
'{"str":"string","num":0,"obj":{"foo":"foo"},"arr":[1,2,3],"bool":true,"nil":null,"fn":function echo(arg) { return arg; },"re":/([^\\s]+)/g}'
49+
'{"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}'
4950
```
5051

5152
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

+20-12
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ See the accompanying LICENSE file for terms.
66

77
'use strict';
88

9-
var isRegExp = require('util').isRegExp;
10-
119
// Generate an internal UID to make the regexp pattern harder to guess.
1210
var UID = Math.floor(Math.random() * 0x10000000000).toString(16);
13-
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R)-' + UID + '-(\\d+)__@"', 'g');
11+
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D)-' + UID + '-(\\d+)__@"', 'g');
1412

1513
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
1614
var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
@@ -39,6 +37,7 @@ module.exports = function serialize(obj, options) {
3937

4038
var functions = [];
4139
var regexps = [];
40+
var dates = [];
4241

4342
// Returns placeholders for functions and regexps (identified by index)
4443
// which are later replaced by their string representation.
@@ -47,18 +46,23 @@ module.exports = function serialize(obj, options) {
4746
return value;
4847
}
4948

50-
var type = typeof value;
49+
// If the value is an object w/ a toJSON method, toJSON is called before
50+
// the replacer runs, so we use this[key] to get the non-toJSONed value.
51+
var origValue = this[key];
52+
var type = typeof origValue;
5153

5254
if (type === 'object') {
53-
if (isRegExp(value)) {
54-
return '@__R-' + UID + '-' + (regexps.push(value) - 1) + '__@';
55+
if(origValue instanceof RegExp) {
56+
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
5557
}
5658

57-
return value;
59+
if(origValue instanceof Date) {
60+
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
61+
}
5862
}
5963

6064
if (type === 'function') {
61-
return '@__F-' + UID + '-' + (functions.push(value) - 1) + '__@';
65+
return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';
6266
}
6367

6468
return value;
@@ -85,14 +89,18 @@ module.exports = function serialize(obj, options) {
8589
// regexps and functions are serialized and added back to the string.
8690
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
8791

88-
if (functions.length === 0 && regexps.length === 0) {
92+
if (functions.length === 0 && regexps.length === 0 && dates.length === 0) {
8993
return str;
9094
}
9195

92-
// Replaces all occurrences of function and regexp placeholders in the JSON
93-
// string with their string representations. If the original value can not
94-
// be found, then `undefined` is used.
96+
// Replaces all occurrences of function, regexp and date placeholders in the
97+
// JSON string with their string representations. If the original value can
98+
// not be found, then `undefined` is used.
9599
return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) {
100+
if (type === 'D') {
101+
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
102+
}
103+
96104
if (type === 'R') {
97105
return regexps[valueIndex].toString();
98106
}

test/unit/serialize.js

+20
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,26 @@ describe('serialize( obj )', function () {
162162
});
163163
});
164164

165+
describe('dates', function () {
166+
it('should serialize dates', function () {
167+
var d = new Date('2016-04-28T22:02:17.156Z');
168+
expect(serialize(d)).to.be.a('string').equal('new Date("2016-04-28T22:02:17.156Z")');
169+
expect(serialize({t: [d]})).to.be.a('string').equal('{"t":[new Date("2016-04-28T22:02:17.156Z")]}');
170+
});
171+
172+
it('should deserialize a date', function () {
173+
var d = eval(serialize(new Date('2016-04-28T22:02:17.156Z')));
174+
expect(d).to.be.a('Date');
175+
expect(d.toISOString()).to.equal('2016-04-28T22:02:17.156Z');
176+
});
177+
178+
it('should deserialize a string that is not a valid date', function () {
179+
var d = eval(serialize('2016-04-28T25:02:17.156Z'));
180+
expect(d).to.be.a('string');
181+
expect(d).to.equal('2016-04-28T25:02:17.156Z');
182+
});
183+
});
184+
165185
describe('XSS', function () {
166186
it('should encode unsafe HTML chars to Unicode', function () {
167187
expect(serialize('</script>')).to.equal('"\\u003C\\u002Fscript\\u003E"');

0 commit comments

Comments
 (0)