So the solution I ended up implementing is not great but it does the trick until I find a better way.
Ember uses Ajax to form the request, so what I did was override 'query' in my JSONAPIAdapter to pass through a custom set of options. My query now looks like this:
query(store, type, query) {
let url = this.buildURL(type.modelName, null, null, 'query', query);
if (this.sortQueryParams) {
query = this.sortQueryParams(query);
}
query = decodeURI(Ember.$.param(query));
let options = {
"processData": false,
"data": query
};
return this.ajax(url, 'GET', options);
},
The key was to stop ajax's automatic processing of the data object, and decode the result of jquery's 'param()' helper function, which is used to convert the query param object into a serialized string.
The result is a decoded query param string like so:
/v1/listing?filter[category]=123