Skip to content

fix(buildURL): preserve encoded colon (%3A) for JSON-stringified params#7290

Closed
Amaan-pathan wants to merge 1 commit intoaxios:v1.xfrom
Amaan-pathan:fix/params-encoding
Closed

fix(buildURL): preserve encoded colon (%3A) for JSON-stringified params#7290
Amaan-pathan wants to merge 1 commit intoaxios:v1.xfrom
Amaan-pathan:fix/params-encoding

Conversation

@Amaan-pathan
Copy link
Copy Markdown

Description

This PR fixes an issue where colons inside JSON-stringified params were not being consistently encoded. In some environments Axios was converting %3A back to :, while in others it preserved the encoding. This led to unpredictable behavior when APIs expected strictly encoded JSON in query parameters.

To address this, I added a small helper (isJSONLike) that detects when a param value looks like JSON (e.g., "{...}" or "[...]"). If the value is JSON-formatted, the colon remains encoded as %3A. For all non-JSON values, Axios continues to follow its existing encoding rules, so backward compatibility is preserved.

What’s Changed

  • Added isJSONLike check inside encode()

  • Ensured JSON-stringified params keep %3A instead of decoding to :

  • Updated buildURL tests to cover:

  • JSON-like params

  • nested objects

  • arrays, dates, and special characters

  • custom serializer functions

  • URLs that already contain a query string

@nidhishgajjar

This comment was marked as spam.

1 similar comment
@nidhishgajjar

This comment was marked as spam.

@jasonsaayman
Copy link
Copy Markdown
Member

Thanks for the writeup, and apologies for the slow turnaround.

I want to push back on the framing first: encode() in lib/helpers/buildURL.js runs identically on every platform. It always decodes %3A back to : because that's the deliberate, documented behaviour (see the JSDoc on the function). The output is RFC 3986 compliant for the query component, since :, $, , are valid sub-delims/gen-delims there. So this isn't a bug we're patching, it's a long-standing convention some strict backends don't accept.

That distinction matters because the proposed fix would change behaviour silently for everyone who currently relies on the readable form. Anyone snapshotting URLs, matching them server-side, or just logging them is going to see a sudden shift in JSON-shaped params with no opt-out.

A few other concerns with the heuristic itself:

  • JSON.parse runs on every string param that starts with { or [. It's a small cost, but it lands on the encode hot path, and it's paid by everyone for a behaviour most people don't need.
  • The trigger is "did JSON.parse succeed", which gets weird at the edges. "{name}" keeps the old encoding. '[1, 2]' used as an opaque identifier flips to the new one. '{"trailing": "comma",}' stays old because it doesn't parse. Same shape, different behaviour, hard to debug.
  • A backend strict enough to require %3A in JSON values almost always wants it in non-JSON values too. This patch only handles one half of that user's problem.
  • There's no regression test pinning down that { filter: 'time:12:00' } still emits time:12:00. Green CI only proves the new case works.

The right answer for strict encoding is already in the public API: paramsSerializer.encode. Per-request or on instance defaults:

axios.get('/foo', {
  params: { filter: JSON.stringify({ startedAt: '2025-01-23' }) },
  paramsSerializer: { encode: encodeURIComponent }
});

That's explicit, scoped, and doesn't change behaviour for anyone else. The reason this isn't more obvious is that it's underdocumented, which is a fair complaint and on us. I've opened #10809 to add a strict-encoding example to the paramsSerializer docs.

Closing this in favour of the docs fix. Genuinely appreciate the issue being raised — getting the escape hatch documented is the right outcome.

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

Successfully merging this pull request may close these issues.

3 participants