javascripttypescriptaxiosinterceptorutc

Customizing Date Serialization in Axios


By default, when a JavaScript object contains a Date object, Axios serializes it into UTC. This means that the time transmitted is converted using the time zone. This doesn't work for my use case. I need to send the time without the time zone conversion to UTC.

I implemented a custom serializer based on #1548, but it has no effect. I have verified that the config is being modified, but when I look at the request payload, the dates are still using UTC (2022-02-03T04:59:00.000Z).

Note: My logic is more complicated, I am using toLocalString() in the code below to simplify the example.

const a = axios.create()
a.interceptors.request.use((config) => {
  config.paramsSerializer = (params) =>
    qs.stringify(params, {
      serializeDate: (date: Date) => date.toLocaleString(),
    })
  return config
})

Anyone understand why I can't override the default behavior and use my own date formatting?

Environment Axios Version 0.21.1


Solution

  • It's not Axios doing the serialisation. Date implements toJSON() which is employed when your objects (containing dates) are serialised via JSON.stringify()...

    The instances of Date implement the toJSON() function by returning a string (the same as date.toISOString()). Thus, they are treated as strings.

    The paramSerializer is for serialising query params which is why it didn't apply to your request body.

    To transform the request body, you want to use transformRequest.

    Here's a generic, recursive transformer that finds dates and maps them to localised strings

    import axios, { AxiosRequestTransformer } from 'axios';
    
    const isPlainObject = (v: unknown) =>
      Object.prototype.toString.call(v) === '[object Object]';
    
    const dateTransformer: AxiosRequestTransformer = (data) => {
      if (data instanceof Date) {
        // do your specific formatting here
        return data.toLocaleString();
      }
      if (Array.isArray(data)) {
        return data.map((val) => dateTransformer(val));
      }
      if (isPlainObject(data)) {
        return Object.fromEntries(
          Object.entries(data).map(([key, val]) => [key, dateTransformer(val)]),
        );
      }
      return data;
    };
    

    You can then register this transformer in your Axios instance which will apply to all PUT, POST, PATCH and DELETE requests.

    const instance = axios.create({
      transformRequest: [ dateTransformer ].concat(axios.defaults.transformRequest)
    })
    
    instance.post(url, data)
    

    If the generic version seems a little heavy-handed, you can either transform the object yourself before posting...

    const body = {
      date: new Date()
    }
    
    axios.post(url, {
      ...body,
      date: body.date.toLocaleString()
    })
    

    or register an inline transformer

    axios.post(url, body, {
      transformRequest: [
        data => ({
          ...data,
          date: data.date.toLocaleString()
        }),
        ...axios.defaults.transformRequest
      ]
    })