1
\$\begingroup\$

I was looking for a similar JavaScript method for http_build_query but did not find but create my own and here I am a bit confused to decide the right approach.

Here is the problem. I have an object of params and need to create query string to append with URL with help of encodeURIComponent:

const params = {
    "name": ["alpha &", "beta"],
    "id": ["one", "&two=2"],
    "from": 1533735971000,
    "to": 1533822371147,
    "status": true
};

Now there are two approaches when there is an array of strings are there. I name them pre-encode and post-encode for now.

pre-encode

Apply encodeURIComponent to individual items and then join all items.

const params = {
  "name": ["alpha &", "beta"],
  "id": ["one", "&two=2"],
  "from": 1533735971000,
  "to": 1533822371147,
  "status": true
};

const output = Object.entries(params).map((pair) => {
  const [key, val] = pair;
  if (val instanceof Array && val.length > 0) {
    let pre_encode = val.map(encodeURIComponent).join(',');
    console.log({pre_encode});
    const pre_qs = `${encodeURIComponent(key)}=${pre_encode}`;
    console.log({pre_qs})
    return pre_qs;
  } else {
    return pair.map(encodeURIComponent).join('=');
  }

}).join('&');


console.log({output});

Output

"name=alpha%20%26,beta&id=one,%26two%3D2&from=1533735971000&to=1533822371147&status=true"


post-encode

First, join all items then apply encodeURIComponent.

const params = {
  "name": ["alpha &", "beta"],
  "id": ["one", "&two=2"],
  "from": 1533735971000,
  "to": 1533822371147,
  "status": true
};

const output = Object.entries(params).map((pair) => {
  const [key, val] = pair;
  if (val instanceof Array && val.length > 0) {
    let post_encode = encodeURIComponent(val.join(','));
    console.log({post_encode});
    const post_qs = `${encodeURIComponent(key)}=${post_encode}`;
    console.log({post_qs})
    return post_qs;
  } else {
    return pair.map(encodeURIComponent).join('=');
  }

}).join('&');


console.log({output});

Output

"name=alpha%20%26%2Cbeta&id=one%2C%26two%3D2&from=1533735971000&to=1533822371147&status=true"

Which approach is the more efficient and right and how to handle null and undefined values?

\$\endgroup\$
1
\$\begingroup\$

Your main question

I have trouble understanding it, because:

  • minor reason:
    (unless I'm missing something obvious) whatever way you encode the array parts, the resulting query string is quite valid in both cases;
    so the only possible criterion for choosing a method rather than the other one would be to find which one is faster... and this sounds a bit overkill!
  • major reason:
    you initially referred to the PHP http_build_query() function, so I'd expect you work the same, i.e. for a given key: [val_1, val_2]:
    • you're currently returning key=val_1,val_2
    • while it should be key=val_1&key=val_2

To match the latter case, I'd suggest something like this:

const output = Object.entries(params).map(
  (pair) => {
    let [key, val] = pair;
    // normalize val to always an array
    val = (val instanceof Array) ? val : [val];
    // encode key only once
    key = encodeURIComponent(key);
    // then make a different query string for each val member
    return val.map(
      (member) => `${key}=${encodeURIComponent(member)}`
    ).join('&');
  }
).join('&');

Your complementary question

To implement null or undefined, first you have to decide how they must be managed: they might be simply omitted, or included with no value (its often useful to have query params where only its presence/absence is meaningful for the underlying application).

Anyway you may simply add any of these capabilities; instead of

    return val.map(
      (member) => `${key}=${encodeURIComponent(member)}`
    ).join('&');

you can include null/undefined params simply with:

return val.map(
  (member) => (member ? `${key}=${encodeURIComponent(member)}` : `${key}`)
).join('&');

(NOTE: it's up to you to enhance this with more precise tests if desired, in order to keep 0 as key=0, or '' as key=)

or you can omit them with a slightly different method:

return val.reduce(
  (result, member) => {
    if (member) {
      result.push(`${key}=${encodeURIComponent(member)}`);
    }
    return result;
  },
  []
).join('&');
\$\endgroup\$
  • \$\begingroup\$ Thank you for the suggestions. I was using .filter to remove null and undefined key and value prior to encoding. additionally using I need query in key=val_1,val_2 format and I think that is right way instead of adding each key and value separately. isn't it. does server treat these both key=val1&key=val2 , key=val1,val2 as same or it depends on mechanism how they read the query params in my case BE is java? \$\endgroup\$ – diEcho Aug 11 '18 at 21:23
  • 1
    \$\begingroup\$ @pro.mean I can't say anything really pertinent about java. But you referred to PHP, and you can check that it's how it works. Subsequently I tend to think that it'd be the same with java, since browsers are necessarily agnostic with regard to server languages. In PHP, a query string like key=val1&key=val2 is "rendered" inside of the $_GETarray as a member like 'key' => ['val1', 'val2']. \$\endgroup\$ – cFreed Aug 11 '18 at 23:28
  • \$\begingroup\$ if (member) does not prevent null value \$\endgroup\$ – diEcho Aug 13 '18 at 6:53
  • \$\begingroup\$ @pro.mean I must insist: if (member) prevents null, undefined, 0, and '' (it's why I added a comment about the two latters). Tested! \$\endgroup\$ – cFreed Aug 13 '18 at 11:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.