Skip to content
Permalink
Browse files

[New] `stringily`: Add `serializeDate` option to customize Date seria…

…lization.

Fixes #159.
  • Loading branch information...
ljharb committed Jul 21, 2016
1 parent 937e5f7 commit 44df2a55de7a5b0617538a489c052d72f5c5a00d
Showing with 110 additions and 15 deletions.
  1. +3 −3 .eslintrc
  2. +11 −0 README.md
  3. +47 −7 lib/stringify.js
  4. +49 −5 test/stringify.js
@@ -4,12 +4,12 @@
"extends": "@ljharb",

"rules": {
"complexity": [2, 22],
"complexity": [2, 23],
"consistent-return": [1],
"id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
"indent": [2, 4],
"max-params": [2, 9],
"max-statements": [2, 36],
"max-params": [2, 10],
"max-statements": [2, 38],
"no-extra-parens": [1],
"no-continue": [1],
"no-magic-numbers": 0,
@@ -290,6 +290,17 @@ The delimiter may be overridden with stringify as well:
assert.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
```

If you only want to override the serialization of `Date` objects, you can provide a `serializeDate` option:

```javascript
var date = new Date(7);
assert.equal(qs.stringify({ a: date }), 'a=1970-01-01T00:00:00.007Z'.replace(/:/g, '%3A'));
assert.equal(
qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } }),
'a=7'
);
```

Finally, you can use the `filter` option to restrict which keys will be included in the stringified output.
If you pass a function, it will be called for each key to obtain the replacement value. Otherwise, if you
pass an array, it will be used to select properties and array indices for stringification:
@@ -14,20 +14,25 @@ var arrayPrefixGenerators = {
}
};

var toISO = Date.prototype.toISOString;

var defaults = {
delimiter: '&',
strictNullHandling: false,
skipNulls: false,
encode: true,
encoder: Utils.encode
encoder: Utils.encode,
serializeDate: function (date) {
return toISO.call(date);
}
};

var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) {
var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots, serializeDate) {
var obj = object;
if (typeof filter === 'function') {
obj = filter(prefix, obj);
} else if (obj instanceof Date) {
obj = obj.toISOString();
obj = serializeDate(obj);
} else if (obj === null) {
if (strictNullHandling) {
return encoder ? encoder(prefix) : prefix;
@@ -65,9 +70,31 @@ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNu
}

if (Array.isArray(obj)) {
values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
values = values.concat(stringify(
obj[key],
generateArrayPrefix(prefix, key),
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate
));
} else {
values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
values = values.concat(stringify(
obj[key],
prefix + (allowDots ? '.' + key : '[' + key + ']'),
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate
));
}
}

@@ -84,6 +111,7 @@ module.exports = function (object, opts) {
var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null;
var sort = typeof options.sort === 'function' ? options.sort : null;
var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate;
var objKeys;
var filter;

@@ -95,7 +123,8 @@ module.exports = function (object, opts) {
filter = options.filter;
obj = filter('', obj);
} else if (Array.isArray(options.filter)) {
objKeys = filter = options.filter;
filter = options.filter;
objKeys = filter;
}

var keys = [];
@@ -130,7 +159,18 @@ module.exports = function (object, opts) {
continue;
}

keys = keys.concat(stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
keys = keys.concat(stringify(
obj[key],
key,
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate
));
}

return keys.join(delimiter);
@@ -414,8 +414,20 @@ test('stringify()', function (t) {

t.test('can sort the keys at depth 3 or more too', function (st) {
var sort = function (a, b) { return a.localeCompare(b); };
st.equal(qs.stringify({ a: 'a', z: { zj: {zjb: 'zjb', zja: 'zja'}, zi: {zib: 'zib', zia: 'zia'} }, b: 'b' }, { sort: sort, encode: false }), 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb');
st.equal(qs.stringify({ a: 'a', z: { zj: {zjb: 'zjb', zja: 'zja'}, zi: {zib: 'zib', zia: 'zia'} }, b: 'b' }, { sort: null, encode: false }), 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b');
st.equal(
qs.stringify(
{ a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
{ sort: sort, encode: false }
),
'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb'
);
st.equal(
qs.stringify(
{ a: 'a', z: { zj: { zjb: 'zjb', zja: 'zja' }, zi: { zib: 'zib', zia: 'zia' } }, b: 'b' },
{ sort: null, encode: false }
),
'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b'
);
st.end();
});

@@ -438,9 +450,7 @@ test('stringify()', function (t) {

t.test('throws error with wrong encoder', function (st) {
st.throws(function () {
qs.stringify({}, {
encoder: 'string'
});
qs.stringify({}, { encoder: 'string' });
}, new TypeError('Encoder has to be a function.'));
st.end();
});
@@ -458,4 +468,38 @@ test('stringify()', function (t) {
}), 'a=b');
st.end();
});

t.test('serializeDate option', function (st) {
var date = new Date();
st.equal(
qs.stringify({ a: date }),
'a=' + date.toISOString().replace(/:/g, '%3A'),
'default is toISOString'
);

var mutatedDate = new Date();
mutatedDate.toISOString = function () {
throw new SyntaxError();
};
st.throws(function () {
mutatedDate.toISOString();
}, SyntaxError);
st.equal(
qs.stringify({ a: mutatedDate }),
'a=' + Date.prototype.toISOString.call(mutatedDate).replace(/:/g, '%3A'),
'toISOString works even when method is not locally present'
);

var specificDate = new Date(6);
st.equal(
qs.stringify(
{ a: specificDate },
{ serializeDate: function (d) { return d.getTime() * 7; } }
),
'a=42',
'custom serializeDate function called'
);

st.end();
});
});

0 comments on commit 44df2a5

Please sign in to comment.
You can’t perform that action at this time.