745

Is there a fast and simple way to encode a JavaScript object into a string that I can pass via a GET request?

No jQuery, no other frameworks—just plain JavaScript :)

8
  • 1
    Why can't JQuery be a solution if there is an appropriate one for your solution?
    – eaglei22
    Commented Mar 30, 2017 at 22:38
  • 5
    @eaglei22 because at the time I was working on a project for an IPTV set top box device and no external libraries were allowed. ;-)
    – napolux
    Commented Mar 31, 2017 at 8:22
  • 1
    Thanks for the response. I see this specification from time to time and always wondered a scenario why. Well, now I got one, thanks! :)
    – eaglei22
    Commented Mar 31, 2017 at 13:48
  • 27
    @eaglei22 Because sometimes you don't want to load a large library to get one element by id. Commented Jun 26, 2017 at 20:46
  • most browsers support URLSearchParams now...
    – mb21
    Commented Mar 12, 2018 at 17:11

47 Answers 47

983

Like this:

serialize = function(obj) {
  var str = [];
  for (var p in obj)
    if (obj.hasOwnProperty(p)) {
      str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
    }
  return str.join("&");
}

console.log(serialize({
  foo: "hi there",
  bar: "100%"
}));
// foo=hi%20there&bar=100%25

This one also converts recursive objects (using PHP "array" notation for the query string):

serialize = function(obj, prefix) {
  var str = [],
    p;
  for (p in obj) {
    if (obj.hasOwnProperty(p)) {
      var k = prefix ? prefix + "[" + p + "]" : p,
        v = obj[p];
      str.push((v !== null && typeof v === "object") ?
        serialize(v, k) :
        encodeURIComponent(k) + "=" + encodeURIComponent(v));
    }
  }
  return str.join("&");
}

console.log(serialize({
  foo: "hi there",
  bar: {
    blah: 123,
    quux: [1, 2, 3]
  }
}));
// foo=hi%20there&bar%5Bblah%5D=123&bar%5Bquux%5D%5B0%5D=1&bar%5Bquux%5D%5B1%5D=2&bar%5Bquux%5D%5B2%5D=3

21
  • 2
    Won't it break given {foo: [1,2,3], bar: "100%" } ?
    – Quentin
    Commented Nov 11, 2009 at 12:53
  • 2
    @Ofri: For POST requests to a server set up to receive it, JSON is a good choice. For GET requests, if you're sending anything other than a few simple parameters to the server then it's likely your design is wrong.
    – Tim Down
    Commented Nov 11, 2009 at 14:25
  • 2
    @Marcel That's because the function doesn't check for hasOwnProperty. I've updated your fiddle so now it does: jsfiddle.net/rudiedirkx/U5Tyb/1
    – Rudie
    Commented Jan 5, 2013 at 18:22
  • 1
    @TimDown Regarding your comment sending simple parameters in GET requests. I do not agree. Grouping parameters into arrays might turn handy as PHP in the server side finds a ready-steady associative array to go. I can't see why this is wrong as a design. Commented Nov 27, 2015 at 10:15
  • 2
    Is the 'if (obj.hasOwnProperty(prop))' necessary? The for in statement loop just over the properties of the object so calling hasOwnProperty always evaluate to true
    – Arnon
    Commented May 26, 2016 at 12:07
833

Just use URLSearchParams This works in all current browsers

new URLSearchParams(object).toString()
10
  • 158
    @EddieMongeJr Query strings are key-value pairs by design, you shouldn't even want to serialize nested objects This answer is the modern way to go. Upvotes needed. Commented Aug 6, 2019 at 15:12
  • 21
    Yes they are key value pairs but there is nothing that says that the value can't be a string encoded object. Also, the original questions asks for a "Javascript Object into a string", which can have nested properties Commented Aug 7, 2019 at 16:38
  • 4
    @EddieMongeJr even the accepted answer (and the others after a briefly look) doesn't support nested object. You can stringify the nested objects before you can to URLSearchParams
    – Mosh Feu
    Commented Jan 13, 2020 at 6:30
  • 3
    it doesn't accept objects as parameters, only strings
    – bokkie
    Commented Jun 19, 2020 at 16:26
  • 2
    only if one is ok with encoding space as +. many backend solutions break with this one Commented Jul 30, 2021 at 2:10
252

jQuery has a function for this, jQuery.param(). If you're already using it, you can use this:

Example:

var params = { width:1680, height:1050 };
var str = jQuery.param( params );

str now contains width=1680&height=1050.

15
  • 166
    quoting Napolux (the OP): "just plain Javascript". :P
    – Sk8erPeter
    Commented Jul 18, 2012 at 15:53
  • 6
    jQuery.param() has sinister behavior. Try to execute var a = []; a[2564] = 12; console.log(jQuery.param({ propertylist: a })); to see what I mean.
    – akond
    Commented Aug 14, 2013 at 12:32
  • 16
    @akond The jQuery documentation specifically says that you may not pass in a bare array.
    – Ariel
    Commented Jun 26, 2014 at 20:47
  • 4
    @Ariel He's not passing in a bare array. He's passing in an array with only one value at index 2564. To demonstrate: var a = []; a[5] = 'foo'; jQuery.param({ parameters: a }); Results in "parameters[]=&parameters[]=&parameters[]=&parameters[]=&parameters[]=&parameters[]=foo". Which, while correct, may not be what you expect.
    – Chris Hall
    Commented Dec 8, 2014 at 20:17
  • 8
    Question has specifically asked Vanilla JS Commented Dec 26, 2017 at 10:57
175
+500

I suggest using the URLSearchParams interface:

const searchParams = new URLSearchParams();
const params = {foo: "hi there", bar: "100%" };
Object.keys(params).forEach(key => searchParams.append(key, params[key]));
console.log(searchParams.toString())

Or by passing the search object into the constructor like this:

const params = {foo: "hi there", bar: "100%" };
const queryString = new URLSearchParams(params).toString();
console.log(queryString);

6
  • If you're not gonna support IE (which is pretty common now) and some specific mobile versions, this is the best answer, as it is plain JavaScript. Commented Jan 23, 2019 at 10:13
  • 7
    @bmaggi doesn't work with nested properties {a: { 1: 'test', 2: 'test2'}} Expected: a[1]=test&a[2]=test2
    – SergkeiM
    Commented Feb 19, 2021 at 8:40
  • @bravemaster It's a great solution, especially for node developers. Thank you! Commented Mar 24, 2021 at 15:47
  • Note that these days, in modern environments, you can use Object.entries if your starting point is an object as in the above: const searchParams = new URLSearchParams(Object.entries(params)); Commented May 31, 2021 at 9:20
  • Does this support object nesting? Commented Aug 10, 2021 at 15:04
139

Use:

Object.keys(obj).reduce(function(a,k){a.push(k+'='+encodeURIComponent(obj[k]));return a},[]).join('&')

I like this one-liner, but I bet it would be a more popular answer if it matched the accepted answer semantically:

function serialize( obj ) {
    let str = '?' + Object.keys(obj).reduce(function(a, k){
        a.push(k + '=' + encodeURIComponent(obj[k]));
        return a;
    }, []).join('&');
    return str;
}
9
  • 1
    A dedicated line for the reduct function would greatly improve the readability though. Commented Apr 20, 2015 at 14:48
  • 60
    Using .map() instead of .reduce() would be even simpler: Object.keys(obj).map(k => k + '=' + encodeURIComponent(obj[k])).join('&') Commented Apr 21, 2015 at 13:34
  • 2
    Just to note that Object.keys is only available in IE >= 9
    – Johnston
    Commented Jun 2, 2015 at 20:26
  • 6
    Further improved @Jannes code using ES6 templates instead of concatenation - Object.keys(obj).map(k => `${k}=${encodeURIComponent(obj[k])}`).join('&')
    – csilk
    Commented Jan 25, 2017 at 0:16
  • 3
    This answer would be even more popular if you used a less generic term than serialize for your edit's function name, perhaps encodeAsQueryString. Otherwise, everyone has to rename it for actual use - or worse, not rename it. Commented Feb 26, 2018 at 19:21
120

Here's a one liner in ES6:

Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
6
  • replace key w/ k and you're golden Commented Feb 24, 2016 at 17:02
  • 23
    Warning! This only works on shallow objects. If you have a top-level property that is another object, this one liner will output "key=%5Bobject%20Object%5D". Just as a heads up. Commented Apr 5, 2016 at 18:47
  • 7
    Also, this doesn't spit up arrays. I got export?actions[]=finance,create,edit when it should have export?actions[]=finance&actions[]=create&actions[]=edit as is the awful standard. Commented Aug 11, 2016 at 3:15
  • 7
    Arrays are pretty much always "you're on your own" because URL arguments are just strings as far as the spec is concerned, so you're on the hook to make anything that isn't a single string gets read correctly by the server you're calling. actions[] is PHP notation; Django uses multiple action instead (no [] suffix); some other ORM/CMS require comma-separated lists, etc. So "if it's not simple strings, first make sure you know what your server even wants". Commented Jan 23, 2017 at 19:01
  • do we really need encodeURIComponent() on the key as well? Commented Jun 15, 2018 at 1:12
70

With Node.js v6.6.3

const querystring = require('querystring')

const obj = {
  foo: 'bar',
  baz: 'tor'
}

let result = querystring.stringify(obj)
// foo=bar&baz=tor

Reference: Query string

3
  • 9
    This shouldn't be downvoted IMO, if it's JS on the server this should be the correct answer. Commented Aug 11, 2016 at 18:11
  • 8
    It seems like it doesn't support nested objects.
    – Yarin Gold
    Commented Jan 1, 2017 at 13:30
  • @MichaelBenin why u think this is only for node server? did u check it?
    – WEBCENTER
    Commented Jul 2, 2020 at 13:07
32

Ruby on Rails and PHP style query builder

This method converts a JavaScript object into a URI query string. It also handles nested arrays and objects (in Ruby on Rails and PHP syntax):

function serializeQuery(params, prefix) {
  const query = Object.keys(params).map((key) => {
    const value  = params[key];

    if (params.constructor === Array)
      key = `${prefix}[]`;
    else if (params.constructor === Object)
      key = (prefix ? `${prefix}[${key}]` : key);

    if (typeof value === 'object')
      return serializeQuery(value, key);
    else
      return `${key}=${encodeURIComponent(value)}`;
  });

  return [].concat.apply([], query).join('&');
}

Example Usage:

let params = {
  a: 100,
  b: 'has spaces',
  c: [1, 2, 3],
  d: { x: 9, y: 8}
}

serializeQuery(params)
// returns 'a=100&b=has%20spaces&c[]=1&c[]=2&c[]=3&d[x]=9&d[y]=8
2
  • 1
    Nice example. I fixed a typo in your answer. By the way, would be interesting if you edit your function to exclude falsy values (null, undefined, NaN, '')... Commented Apr 29, 2017 at 22:44
  • 1
    This is a good example to solve this problem with a well written and incorporates the recursion and type checking needed to solve this issue.
    – Noah
    Commented Jul 14, 2020 at 17:42
26

A small amendment to the accepted solution by user187291:

serialize = function(obj) {
   var str = [];
   for(var p in obj){
       if (obj.hasOwnProperty(p)) {
           str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
       }
   }
   return str.join("&");
}

Checking for hasOwnProperty on the object makes JSLint and JSHint happy, and it prevents accidentally serializing methods of the object or other stuff if the object is anything but a simple dictionary. See the paragraph on for statements on Code Conventions for the JavaScript Programming Language.

23

Here is a one-liner:

const encoded = Object.entries(obj).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
4
  • 2
    Object.entries is not supported in IE.
    – MBouwman
    Commented Apr 18, 2019 at 13:28
  • 1
    @MBouwman of course, IE is broken beyond good and evil, that's why you have to use babel/core-js
    – chpio
    Commented Jul 3, 2019 at 16:27
  • @chpio Babel/core-js does not support Object.entries if I'm right.
    – MBouwman
    Commented Sep 20, 2019 at 12:18
  • core has support for Object.entries: github.com/zloirock/core-js/blob/master/… and even the old corejs2 babel runtime transform does support it as well github.com/babel/babel/blob/…
    – chpio
    Commented Sep 23, 2019 at 14:00
12

If you need to send arbitrary objects, then GET is a bad idea since there are limits to the lengths of URLs that user agents and web servers will accepts. My suggestion would be to build up an array of name-value pairs to send and then build up a query string:

function QueryStringBuilder() {
    var nameValues = [];

    this.add = function(name, value) {
        nameValues.push( {name: name, value: value} );
    };

    this.toQueryString = function() {
        var segments = [], nameValue;
        for (var i = 0, len = nameValues.length; i < len; i++) {
            nameValue = nameValues[i];
            segments[i] = encodeURIComponent(nameValue.name) + "=" + encodeURIComponent(nameValue.value);
        }
        return segments.join("&");
    };
}

var qsb = new QueryStringBuilder();
qsb.add("veg", "cabbage");
qsb.add("vegCount", "5");

alert( qsb.toQueryString() );
8

Use:

const toQueryString = obj => "?".concat(Object.keys(obj).map(e => `${encodeURIComponent(e)}=${encodeURIComponent(obj[e])}`).join("&"));

const data = {
  offset: 5,
  limit: 10
};

toQueryString(data); // => ?offset=5&limit=10

Or use a predefined feature

const data = {
  offset: 5,
  limit: 10
};

new URLSearchParams(data).toString(); // => ?offset=5&limit=10

Note

Both the above methods will set the value as null if not present. If you want not to set the query parameter if value is null then use:

const toQueryString = obj => "?".concat(Object.keys(obj).map(e => obj[e] ? `${encodeURIComponent(e)}=${encodeURIComponent(obj[e])}` : null).filter(e => !!e).join("&"));


const data = {
  offset: null,
  limit: 10
};

toQueryString(data); // => "?limit=10" else with above methods "?offset=null&limit=10"

You can freely use any method.

1
  • 1
    URLSearchParams should be the only answer at this point in history. Setting value == null is done because it will always evaluate as falsey consistently. This allows the service endpoint to consistently check the parameter value as truthy or falsey without the extra checks that come with String of length == 0 or String == undefined. Your solution to exclude if null works, but I think it's a better design pattern to leave null values there since most APIs are going to be checking for them anyway in most scenarios.
    – Wes
    Commented Apr 1, 2022 at 17:39
7

A little bit look better

objectToQueryString(obj, prefix) {
    return Object.keys(obj).map(objKey => {
        if (obj.hasOwnProperty(objKey)) {
            const key = prefix ? `${prefix}[${objKey}]` : objKey;
            const value = obj[objKey];

            return typeof value === "object" ?
                this.objectToQueryString(value, key) :
                `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
        }

        return null;
    }).join("&");
}
7

This one skips null/undefined values

export function urlEncodeQueryParams(data) {
    const params = Object.keys(data).map(key => data[key] ? `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}` : '');
    return params.filter(value => !!value).join('&');
}
7

Here's the CoffeeScript version of the accepted answer.

serialize = (obj, prefix) ->
  str = []
  for p, v of obj
    k = if prefix then prefix + "[" + p + "]" else p
    if typeof v == "object"
      str.push(serialize(v, k))
    else
      str.push(encodeURIComponent(k) + "=" + encodeURIComponent(v))

  str.join("&")
0
6

Here's a concise & recursive version with Object.entries. It handles arbitrarily nested arrays, but not nested objects. It also removes empty elements:

const format = (k,v) => v !== null ? `${k}=${encodeURIComponent(v)}` : ''

const to_qs = (obj) => {
    return [].concat(...Object.entries(obj)
                       .map(([k,v]) => Array.isArray(v) 
                          ? v.map(arr => to_qs({[k]:arr})) 
                          : format(k,v)))
           .filter(x => x)
           .join('&');
}

E.g.:

let json = { 
    a: [1, 2, 3],
    b: [],              // omit b
    c: 1,
    d: "test&encoding", // uriencode
    e: [[4,5],[6,7]],   // flatten this
    f: null,            // omit nulls
    g: 0
};

let qs = to_qs(json)

=> "a=1&a=2&a=3&c=1&d=test%26encoding&e=4&e=5&e=6&e=7&g=0"
1
  • This version did the job for me when dealing with nested arrays. Made a slight tweak to use Ruby/PHP-style array keys but otherwise works great.
    – nickb
    Commented Nov 27, 2018 at 22:15
6

There another popular library, qs. You can add it by:

yarn add qs

And then use it like this:

import qs from 'qs'

const array = { a: { b: 'c' } }
const stringified = qs.stringify(array, { encode: false })

console.log(stringified) //-- outputs a[b]=c
1
  • 1
    That's because the OP wanted to use plain javascript, no external libraries. Commented Mar 20, 2021 at 23:15
5

I made a comparison of JSON stringifiers and the results are as follows:

JSON:    {"_id":"5973782bdb9a930533b05cb2","isActive":true,"balance":"$1,446.35","age":32,"name":"Logan Keller","email":"[email protected]","phone":"+1 (952) 533-2258","friends":[{"id":0,"name":"Colon Salazar"},{"id":1,"name":"French Mcneil"},{"id":2,"name":"Carol Martin"}],"favoriteFruit":"banana"}
Rison:   (_id:'5973782bdb9a930533b05cb2',age:32,balance:'$1,446.35',email:'[email protected]',favoriteFruit:banana,friends:!((id:0,name:'Colon Salazar'),(id:1,name:'French Mcneil'),(id:2,name:'Carol Martin')),isActive:!t,name:'Logan Keller',phone:'+1 (952) 533-2258')
O-Rison: _id:'5973782bdb9a930533b05cb2',age:32,balance:'$1,446.35',email:'[email protected]',favoriteFruit:banana,friends:!((id:0,name:'Colon Salazar'),(id:1,name:'French Mcneil'),(id:2,name:'Carol Martin')),isActive:!t,name:'Logan Keller',phone:'+1 (952) 533-2258'
JSURL:   ~(_id~'5973782bdb9a930533b05cb2~isActive~true~balance~'!1*2c446.35~age~32~name~'Logan*20Keller~email~'logankeller*40artiq.com~phone~'*2b1*20*28952*29*20533-2258~friends~(~(id~0~name~'Colon*20Salazar)~(id~1~name~'French*20Mcneil)~(id~2~name~'Carol*20Martin))~favoriteFruit~'banana)
QS:      _id=5973782bdb9a930533b05cb2&isActive=true&balance=$1,446.35&age=32&name=Logan Keller&[email protected]&phone=+1 (952) 533-2258&friends[0][id]=0&friends[0][name]=Colon Salazar&friends[1][id]=1&friends[1][name]=French Mcneil&friends[2][id]=2&friends[2][name]=Carol Martin&favoriteFruit=banana
URLON:   $_id=5973782bdb9a930533b05cb2&isActive:true&balance=$1,446.35&age:32&name=Logan%20Keller&[email protected]&phone=+1%20(952)%20533-2258&friends@$id:0&name=Colon%20Salazar;&$id:1&name=French%20Mcneil;&$id:2&name=Carol%20Martin;;&favoriteFruit=banana
QS-JSON: isActive=true&balance=%241%2C446.35&age=32&name=Logan+Keller&email=logankeller%40artiq.com&phone=%2B1+(952)+533-2258&friends(0).id=0&friends(0).name=Colon+Salazar&friends(1).id=1&friends(1).name=French+Mcneil&friends(2).id=2&friends(2).name=Carol+Martin&favoriteFruit=banana

The shortest among them is URL Object Notation.

5

I have a simpler solution that does not use any third-party library and is already apt to be used in any browser that has "Object.keys" (aka all modern browsers + Edge + Internet Explorer):

In ES5:

function(a){
    if( typeof(a) !== 'object' )
        return '';
    return `?${Object.keys(a).map(k=>`${k}=${a[k]}`).join('&')}`;
}

In ES3:

function(a){
    if( typeof(a) !== 'object' )
        return '';
    return '?' + Object.keys(a).map(function(k){ return k + '=' + a[k] }).join('&');
}
4

ES6 solution for query string encoding of a JavaScript object

const params = {
  a: 1,
  b: 'query stringify',
  c: null,
  d: undefined,
  f: '',
  g: { foo: 1, bar: 2 },
  h: ['Winterfell', 'Westeros', 'Braavos'],
  i: { first: { second: { third: 3 }}}
}

static toQueryString(params = {}, prefix) {
  const query = Object.keys(params).map((k) => {
    let key = k;
    const value = params[key];

    if (!value && (value === null || value === undefined || isNaN(value))) {
      value = '';
    }

    switch (params.constructor) {
      case Array:
        key = `${prefix}[]`;
        break;
      case Object:
        key = (prefix ? `${prefix}[${key}]` : key);
        break;
    }

    if (typeof value === 'object') {
      return this.toQueryString(value, key); // for nested objects
    }

    return `${key}=${encodeURIComponent(value)}`;
  });

  return query.join('&');
}

toQueryString(params)

"a=1&b=query%20stringify&c=&d=&f=&g[foo]=1&g[bar]=2&h[]=Winterfell&h[]=Westeros&h[]=Braavos&i[first][second][third]=3"
1
  • 1
    Doesn't work for array of objects, like so: [{"a": 1}, {"b": [1,2]}] :(
    – MBI
    Commented Sep 23, 2020 at 13:44
4

A single line to convert an object into a query string in case somebody needs it again:

let Objs = { a: 'obejct-a', b: 'object-b' }

Object.keys(objs).map(key => key + '=' + objs[key]).join('&')

// The result will be a=object-a&b=object-b
4

This is an addition for the accepted solution. This works with objects and array of objects:

parseJsonAsQueryString = function (obj, prefix, objName) {
    var str = [];
    for (var p in obj) {
        if (obj.hasOwnProperty(p)) {
            var v = obj[p];
            if (typeof v == "object") {
                var k = (objName ? objName + '.' : '') + (prefix ? prefix + "[" + p + "]" : p);
                str.push(parseJsonAsQueryString(v, k));
            } else {
                var k = (objName ? objName + '.' : '') + (prefix ? prefix + '.' + p : p);
                str.push(encodeURIComponent(k) + "=" + encodeURIComponent(v));
                //str.push(k + "=" + v);
            }
        }
    }
    return str.join("&");
}

Also I have added objName if you're using object parameters, like in ASP.NET MVC action methods.

4

In ES7, you can write this in one line:

const serialize = (obj) => (Object.entries(obj).map(i => [i[0], encodeURIComponent(i[1])].join('=')).join('&'))
1
  • Can you give a real example? Where's the string?
    – LarryBud
    Commented Apr 13, 2024 at 2:40
3

If you want to convert a nested object recursively and the object may or may not contain arrays (and the arrays may contain objects or arrays, etc), then the solution gets a little more complex. This is my attempt.

I've also added some options to choose if you want to record for each object member at what depth in the main object it sits, and to choose if you want to add a label to the members that come from converted arrays.

Ideally you should test if the thing parameter really receives an object or array.

function thingToString(thing,maxDepth,recordLevel,markArrays){
    //thing: object or array to be recursively serialized
    //maxDepth (int or false):
    // (int) how deep to go with converting objects/arrays within objs/arrays
    // (false) no limit to recursive objects/arrays within objects/arrays
    //recordLevel (boolean):
    //  true - insert "(level 1)" before transcript of members at level one (etc)
    //  false - just 
    //markArrays (boolean):
    //  insert text to indicate any members that came from arrays
    var result = "";
    if (maxDepth !== false && typeof maxDepth != 'number') {maxDepth = 3;}
    var runningDepth = 0;//Keeps track how deep we're into recursion

    //First prepare the function, so that it can call itself recursively
    function serializeAnything(thing){
        //Set path-finder values
        runningDepth += 1;
        if(recordLevel){result += "(level " + runningDepth + ")";}

        //First convert any arrays to object so they can be processed
        if (thing instanceof Array){
            var realObj = {};var key;
            if (markArrays) {realObj['type'] = "converted array";}
            for (var i = 0;i < thing.length;i++){
                if (markArrays) {key = "a" + i;} else {key = i;}
                realObj[key] = thing[i];
            }
            thing = realObj;
            console.log('converted one array to ' + typeof realObj);
            console.log(thing);
        }

        //Then deal with it
        for (var member in thing){
            if (typeof thing[member] == 'object' && runningDepth < maxDepth){
                serializeAnything(thing[member]);
                //When a sub-object/array is serialized, it will add one to
                //running depth. But when we continue to this object/array's
                //next sibling, the level must go back up by one
                runningDepth -= 1;
            } else if (maxDepth !== false && runningDepth >= maxDepth) {
                console.log('Reached bottom');
            } else 
            if (
                typeof thing[member] == "string" || 
                typeof thing[member] == 'boolean' ||
                typeof thing[member] == 'number'
            ){
                result += "(" + member + ": " + thing[member] + ") ";
            }  else {
                result += "(" + member + ": [" + typeof thing[member] + " not supported]) ";
            }
        }
    }
    //Actually kick off the serialization
    serializeAnything(thing);

    return result;

}
0
3

This is a solution that will work for .NET backends out of the box. I have taken the primary answer of this thread and updated it to fit our .NET needs.

function objectToQuerystring(params) {
var result = '';

    function convertJsonToQueryString(data, progress, name) {
        name = name || '';
        progress = progress || '';
        if (typeof data === 'object') {
            Object.keys(data).forEach(function (key) {
                var value = data[key];
                if (name == '') {
                    convertJsonToQueryString(value, progress, key);
                } else {
                    if (isNaN(parseInt(key))) {
                        convertJsonToQueryString(value, progress, name + '.' + key);
                    } else {
                        convertJsonToQueryString(value, progress, name + '[' + key+ ']');
                    }
                }
            })
        } else {
            result = result ? result.concat('&') : result.concat('?');
            result = result.concat(`${name}=${data}`);
        }
    }

    convertJsonToQueryString(params);
    return result;
}
3

I've written a package just for that: object-query-string :)

It supports nested objects, arrays, custom encoding functions, etc. It is lightweight and jQuery-free.

// TypeScript
import { queryString } from 'object-query-string';

// Node.js
const { queryString } = require("object-query-string");

const query = queryString({
    filter: {
        brands: ["Audi"],
        models: ["A4", "A6", "A8"],
        accidentFree: true
    },
    sort: 'mileage'
});

returns

filter[brands][]=Audi&filter[models][]=A4&filter[models][]=A6&filter[models][]=A8&filter[accidentFree]=true&sort=milage
2

After going through some top answers here, I have wrote another implementation that tackles some edge cases as well

function serialize(params, prefix) {                
    return Object.entries(params).reduce((acc, [key, value]) => {
        // remove whitespace from both sides of the key before encoding
        key = encodeURIComponent(key.trim());

        if (params.constructor === Array ) {
          key = `${prefix}[]`;
        } else if (params.constructor === Object) {
          key = (prefix ? `${prefix}[${key}]` : key);
        }

        /**
         *  - undefined and NaN values will be skipped automatically
         *  - value will be empty string for functions and null
         *  - nested arrays will be flattened
         */
        if (value === null || typeof value === 'function') {
            acc.push(`${key}=`);
        } else if (typeof value === 'object') {
            acc = acc.concat(serialize(value, key));
        } else if(['number', 'boolean', 'string'].includes(typeof value) && value === value) { // self-check to avoid NaN
            acc.push(`${key}=${encodeURIComponent(value)}`);
        }

        return acc;
    }, []);
}

function objectToQueryString(queryParameters) {
    return queryParameters ? serialize(queryParameters).join('&'): '';
}

let x = objectToQueryString({
    foo: 'hello world',
    bar: {
      blah: 123,
      list: [1, 2, 3],
        'nested array': [[4,5],[6,7]] // will be flattened
    },
    page: 1,
    limit: undefined, // field will be ignored
    check: false,
    max: NaN, // field will be ignored
    prop: null,
    ' key value': 'with spaces' // space in key will be trimmed out
});
  
console.log(x); // foo=hello%20world&bar[blah]=123&bar[list][]=1&bar[list][]=2&bar[list][]=3&bar[nested%20array][][]=4&bar[nested%20array][][]=5&bar[nested%20array][][]=6&bar[nested%20array][][]=7&page=1&check=false&prop=&key%20value=with%20spaces
2

The previous answers do not work if you have a lot of nested objects.

Instead you can pick the function parameter from jquery-param/jquery-param.js. It worked very well for me!

    var param = function (a) {
    var s = [], rbracket = /\[\]$/,
        isArray = function (obj) {
            return Object.prototype.toString.call(obj) === '[object Array]';
        }, add = function (k, v) {
            v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
            s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
        }, buildParams = function (prefix, obj) {
            var i, len, key;

            if (prefix) {
                if (isArray(obj)) {
                    for (i = 0, len = obj.length; i < len; i++) {
                        if (rbracket.test(prefix)) {
                            add(prefix, obj[i]);
                        } else {
                            buildParams(prefix + '[' + (typeof obj[i] === 'object' ? i : '') + ']', obj[i]);
                        }
                    }
                } else if (obj && String(obj) === '[object Object]') {
                    for (key in obj) {
                        buildParams(prefix + '[' + key + ']', obj[key]);
                    }
                } else {
                    add(prefix, obj);
                }
            } else if (isArray(obj)) {
                for (i = 0, len = obj.length; i < len; i++) {
                    add(obj[i].name, obj[i].value);
                }
            } else {
                for (key in obj) {
                    buildParams(key, obj[key]);
                }
            }
            return s;
        };

    return buildParams('', a).join('&').replace(/%20/g, '+');
};
1

ok, it's a older post but i'm facing this problem and i have found my personal solution.. maybe can help someone else..

     function objToQueryString(obj){
        var k = Object.keys(obj);
        var s = "";
        for(var i=0;i<k.length;i++) {
            s += k[i] + "=" + encodeURIComponent(obj[k[i]]);
            if (i != k.length -1) s += "&";
        }
        return s;
     };
1

URLSearchParams looks good, but it didn't work for nested objects.

Try to use

encodeURIComponent(JSON.stringify(object))
2
  • for me JSON.stringify() worked just fine.
    – showtime
    Commented Nov 5, 2022 at 8:58
  • Re "Try to use": Why is that better? Why is it necessary? Where was it tested (browser (or other environment), operating system, JavaScript things, all incl. versions)? Commented Nov 27, 2022 at 22:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.