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

Serialization for Date is not working when using filter option. #467

Open
joonseokhu opened this issue Mar 7, 2023 · 5 comments
Open

Serialization for Date is not working when using filter option. #467

joonseokhu opened this issue Mar 7, 2023 · 5 comments

Comments

@joonseokhu
Copy link

joonseokhu commented Mar 7, 2023

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;
  }
});
@ljharb
Copy link
Owner

ljharb commented Mar 7, 2023

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.

@dbnar2
Copy link

dbnar2 commented Mar 16, 2023

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?

@joonseokhu
Copy link
Author

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

@jtrbalic
Copy link

jtrbalic commented May 24, 2024

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.

Are there any plans for this to be fixed soon? I am willing to contribute.

@ljharb
Copy link
Owner

ljharb commented May 24, 2024

@carpics if there's a way to provide this capability without a breaking change, i'd love to hear about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants