Serialization for `Date` is not working when using `filter` option.
joonseokhu opened this issue · comments
serialization for Date
type values are skipped when I'm usinng filter
option for custom serialization.
code to reproduce
import qs from 'qs'
class Range {
constructor(
public from: number,
public to: number
) {}
}
const data = {
a: new Range(1, 123),
b: new Date(),
c: [new Date(), new Range(3, 45)],
d: false,
e: { a: 123, b: new Date(), c: new Range(6, 700) },
}
const stringified = qs.stringify(data, {
filter(_, value) {
if (value instanceof Range) {
return `${value.from}...${value.to}`;
}
return value;
}
});
console.log(qs.parse(stringified));
what I expected
{
a: '1...123',
b: '2023-03-07T09:02:41.260Z',
c: [ '2023-03-07T09:02:41.260Z', '3...45' ],
d: 'false',
e: {
a: '123',
b: '2023-03-07T09:02:41.260Z',
c: '6...700'
}
}
what actually worked
{
a: '1...123',
c: [ '3...45' ],
d: 'false',
e: { a: '123', c: '6...700' }
}
so I have to call qs.stringify
like this:
const stringified = qs.stringify(data, {
filter(_, value) {
if (value instanceof Range) {
return `${value.from}...${value.to}`;
}
// I have to serialize `Date` by my self
if (value instanceof Date) {
return value.toISOString()
}
return value;
}
});
Yes, that's true - using the filter
option overrides the default filtering, which includes date serialization, as well as special handling for arrayFormat: 'comma'
.
One possibility would be to pass a third argument to filter
of defaultFilter
, so you could return defaultFilter(_, value);
and you'd get that default serialization - but we couldn't do that by default, or it'd be a breaking change.
Somewhat related to this.
Is there a way I can pass a custom prefix?
For example I have an object:
{ Age: [10,20] }
And want to serialize it so that it turns into:
age[gte]=10&age[lte]=20
The current filter only lets me return the value but not a prefix. Am i missing something here?
@dbnar2 I think there's few options that you can choose.
The point is that you should translate the array into an object, and you can get the querystring with key inside of brakets
qs.stringify({ foo: [1, 2] }, { encodeValuesOnly: true });
// foo[0]=1&foo[1]=2
qs.stringify({ foo: { bar: 1, baz: 2 } }, { encodeValuesOnly: true });
// foo[bar]=1&foo[baz]=2
- translate the array into object when key of the property is what you target.
const data = {
myRange: [10, 20],
}
qs.stringify(data, {
encodeValuesOnly: true,
filter(key, value) {
if (key === 'myRange') {
const [gte, lte] = value
return { gte, lte }
}
// You have to serialize `Date` by yourself
if (value instanceof Date) {
return value.toISOString()
}
return value;
}
});
// myRange[gte]=10&myRange[lte]=20
- translate any array with two number elements
/**
* returns true only if value is an array with 2 number elements.
*/
const isRange = (value: any): value is [number, number] => {
if (!Array.isArray(value)) return false;
if (value.length !== 2) return false;
if (value.some(el => typeof el !== 'number')) return false;
return true;
}
const data = {
myRange: [10, 20],
}
qs.stringify(data, {
encodeValuesOnly: true,
filter(key, value) {
if (isRange(value)) {
const [gte, lte] = value
return { gte, lte }
}
// You have to serialize `Date` by yourself
if (value instanceof Date) {
return value.toISOString()
}
return value;
}
});
// myRange[gte]=10&myRange[lte]=20
- Just make and use some helperFunction before calling
qs
const toNumberRange = ([gte, lte]: [number, number]) => {
return { gte, lte }
}
const data = {
myRange: toNumberRange([10, 20]),
}
qs.stringify(data, {
encodeValuesOnly: true,
});
// myRange[gte]=10&myRange[lte]=20