fix: correct query param serialization for arrays, dates and null - #329
Merged
Conversation
`queryParamsStringify` silently corrupted GET query strings in three ways.
All of them failed silently — the request returned 200 with wrong data
rather than erroring, so callers got no signal the param was never sent.
- Arrays of objects used `join(',')`, stringifying each element via
`String()` and producing `sort=[object Object]`. They are now JSON
encoded, which is the format the API expects. Scalar arrays keep the
comma-separated form (`ids=a,b`), since switching those to JSON would
be a breaking wire-format change.
- `Date` values were pushed without the `${k}=` prefix, dropping the key
and injecting a bare value into the query string
(`?limit=10&2026-08-15T10:00:00.000Z`). They now keep their key and are
URL encoded.
- `null` took the `typeof param === 'object'` branch and serialized as the
literal string `id_gt=null`. It is now skipped, matching `undefined`.
`null` and `undefined` entries within an array are dropped as well, so
that a single empty value can't flip a scalar array from `a,b` to a JSON
array. The array handling lives in `utils/query-params.ts` and resolves
the format in a single pass.
Affected endpoints: ChatApi.getReplies and
VideoApi.queryCallSessionParticipantStats (top-level `sort`), plus
VideoApi.getCallParticipantSessionMetrics and VideoApi.getCallStatsMap
(`Date` bounds).
Verified against the live API: the server validates the JSON `sort` array
and honours it for page selection, and accepts the ISO date bounds.
szuperaz
approved these changes
Aug 17, 2026
oliverlaz
pushed a commit
that referenced
this pull request
Aug 18, 2026
🤖 I have created a release *beep* *boop* --- ## [0.7.64](v0.7.63...v0.7.64) (2026-08-18) ### Features * update to open api version 233.25.1 ([#326](#326)) ([618728a](618728a)) * update to open api version 235.16.3 ([#328](#328)) ([d4f0c22](d4f0c22)) ### Bug Fixes * correct query param serialization for arrays, dates and null ([#329](#329)) ([7f228fc](7f228fc)) * order "types" first in package.json exports ([#330](#330)) ([747e0c6](747e0c6)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
queryParamsStringifysilently corrupted GET query strings in three ways. All of them failed silently — the request returned200with wrong data rather than erroring, so callers got no signal that the parameter was never sent.The bugs
1. Arrays of objects serialized to
[object Object]. The array branch usedparam.join(','), which stringifies each element viaString(). The object branch immediately below it already usedJSON.stringify.2.
Dateparams lost their key. Theinstanceof Datebranch pushedparam.toISOString()with no`${k}=`prefix, dropping the key and injecting a bare, unencoded value into the query string.3.
nullserialized as the literal string"null".typeof null === 'object', so it took the object branch andJSON.stringify(null)producedid_gt=nullon the wire.undefinedwas already dropped correctly.Affected endpoints
Audited every
*Api.tsundersrc/genfor GET query params typed as an array of non-scalars or asDate:ChatApi.getRepliessort?: SortParamRequest[]VideoApi.queryCallSessionParticipantStatssort?: SortParamRequest[]VideoApi.getCallParticipantSessionMetricssince?: Date,until?: DateVideoApi.getCallStatsMapstart_time?: Date,end_time?: DategetRepliesreturned unsorted replies, so an ordering assertion failed with no indication that the sort was never sent. For the Video endpoints, any caller passing a time range silently got an unfiltered one.Not affected:
queryChannels,queryThreads,queryRemindersandgetRetentionPolicyRunsare POST, so their sorts travel in the JSON body.queryUsers,queryMembersandsearchRolesare GET but nestsortinside apayloadobject, which already took the correctJSON.stringifybranch.The fix
Array handling moves to
src/utils/query-params.ts, which resolves the wire format in a single pass:ids=a,b). Switching those to JSON unconditionally — asstream-chat'saxiosParamsSerializerdoes — would be a breaking wire-format change for every string-array param, so the format is chosen per array. If the backend accepts a JSON array everywhere, this could be simplified; that's an API-owner call.null/undefinedentries within an array are dropped, so a single empty value can't flip a scalar array froma,bto a JSON array.Verification
Unit tests cover all four wire formats plus the empty-value cases. Beyond that, the encodings were checked against the live API rather than assumed:
sortarray —sort=notjsonand a bare (non-array) JSON object are both rejected withnot a valid JSON for field 'sort', and a bogus field name is rejected withSorting is only supported on 'created_at' field.getReplieswithlimit=2returns the two oldest replies fordirection: 1and the two newest fordirection: -1, confirming the sort is honoured end-to-end. Note the sort selects the page; in-page order is always chronological.getCallStatsMapwith ISOstart_time/end_timefails on the missing call rather than on the time format, confirming the dates parse.The full suite shows no new failures; the remaining failures are pre-existing on
main(live-API integration tests and a webhook-signature test).