Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Dates #27

Merged
merged 2 commits into from
Jul 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Serialize JavaScript
====================

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

[![npm Version][npm-badge]][npm]
[![Dependency Status][david-badge]][david]
Expand All @@ -11,7 +11,7 @@ Serialize JavaScript to a _superset_ of JSON that includes regular expressions a

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.

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.
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.

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.**

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

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

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

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.
Expand Down
32 changes: 20 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ See the accompanying LICENSE file for terms.

'use strict';

var isRegExp = require('util').isRegExp;

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

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

var functions = [];
var regexps = [];
var dates = [];

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

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

if (type === 'object') {
if (isRegExp(value)) {
return '@__R-' + UID + '-' + (regexps.push(value) - 1) + '__@';
if(origValue instanceof RegExp) {
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
}

return value;
if(origValue instanceof Date) {
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
}
}

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

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

if (functions.length === 0 && regexps.length === 0) {
if (functions.length === 0 && regexps.length === 0 && dates.length === 0) {
return str;
}

// Replaces all occurrences of function and regexp placeholders in the JSON
// string with their string representations. If the original value can not
// be found, then `undefined` is used.
// Replaces all occurrences of function, regexp and date placeholders in the
// JSON string with their string representations. If the original value can
// not be found, then `undefined` is used.
return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) {
if (type === 'D') {
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
}

if (type === 'R') {
return regexps[valueIndex].toString();
}
Expand Down
21 changes: 21 additions & 0 deletions test/unit/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,26 @@ describe('serialize( obj )', function () {
});
});

describe('dates', function () {
it('should serialize dates', function () {
var d = new Date('2016-04-28T22:02:17.156Z');
expect(serialize(d)).to.be.a('string').equal('new Date("2016-04-28T22:02:17.156Z")');
expect(serialize({t: [d]})).to.be.a('string').equal('{"t":[new Date("2016-04-28T22:02:17.156Z")]}');
});

it('should deserialize a date', function () {
var d = eval(serialize(new Date('2016-04-28T22:02:17.156Z')));
expect(d).to.be.a('Date');
expect(d.toISOString()).to.equal('2016-04-28T22:02:17.156Z');
});

it('should deserialize a string that is not a valid date', function () {
var d = eval(serialize('2016-04-28T25:02:17.156Z'));
expect(d).to.be.a('string');
expect(d).to.equal('2016-04-28T25:02:17.156Z');
});
});

describe('XSS', function () {
it('should encode unsafe HTML chars to Unicode', function () {
expect(serialize('</script>')).to.equal('"\\u003C\\u002Fscript\\u003E"');
Expand Down Expand Up @@ -197,6 +217,7 @@ describe('serialize( obj )', function () {
expect(serialize(fn, {isJSON: false})).to.equal('function fn() { return true; }');

expect(serialize(fn, {isJSON: true})).to.equal('undefined');
expect(serialize([1], {isJSON: true, space: 2})).to.equal('[\n 1\n]');
});
});

Expand Down