mobile menu icon

Kata: object literal to query string

Published by Carlos Blé on 07/09/2016

Katas, Learning, JavaScript


No long ago, we had to write a function that takes an object and returns a string to be sent as part of the query string in the request. This was because the communication mechanism was JSONP that works via the GET method. The server was already expecting the specific format shown below and we didn't have access to the server in order to change it and accept just an URL-encoded JSON. The fun started when we realised that an object may be a tree structure with nested objects. We test-drove the solution and found it interesting as a code kata. This is the expected behavior of the function:

expect(objectToQueryString("noObject")).toBe("");
expect(objectToQueryString({})).toBe("");
expect(objectToQueryString({prop: 'value'})).toBe("prop=value");
expect(objectToQueryString({prop: 'value', p2: 'v2'})).toBe("prop=value&p2=v2");
expect(objectToQueryString({p: {p2: 'value'}})).toBe("p[p2]=value");
var obj = {
prop0: "val0",
level1: {
prop1: "val1",
level2:{
prop2:"val2"
}
}
};
expect(objectToQueryString(obj)).toBe(
"prop0=val0&level1[prop1]=value1&level1[level2][prop2]=val2");

This is our implementation:

function objectToQueryString(obj){
if (isNotConvertible(obj)) return "";
let queryString = "";
for (let propertyName in obj){
let propertyValue = obj[propertyName];
if (isAnObject(propertyValue)){
let convertedNestedProperties = objectToQueryString(propertyValue)
.split('&');
let surroundedProperties = surroundNestedWithBrackets(
propertyName,
convertedNestedProperties);
queryString += surroundedProperties.join('&');
}
else {
queryString += `${propertyName}=${propertyValue}`;
}
queryString += '&';
}
return removeLastAmpersand(queryString);
}
function surroundNestedWithBrackets(propertyName, nestedProperties){
let surrounded = [];
for(let i = 0; i < nestedProperties.length; i++){
let nested = nestedProperties[i],
nestedName,
nestedValue;
let isNestedObject = nested.indexOf('[') >= 0;
if (isNestedObject){
nestedName = substringUntil('[', nested);
nestedValue = substringFrom('[', nested);
}
else {
nestedName = substringUntil('=', nested);
nestedValue = substringFrom('=', nested);
}
surrounded.push(`${propertyName}[${nestedName}]${nestedValue}`);
}
return surrounded;
}
function substringUntil(symbol, text){
return text.substring(0, text.indexOf(symbol));
}
function substringFrom(symbol, text){
return text.substring(text.indexOf(symbol));
}
function isNotConvertible(obj ){
return !obj ||
typeof(obj) != 'object' ||
JSON.stringify(obj) =="{}";
}
function isAnObject(val){
return typeof(val) == 'object';
}
function removeLastAmpersand(queryString){
if (queryString.lastIndexOf("&") == queryString.length -1){
return queryString.substring(0, queryString.length -1);
}
return queryString;
}

Note that the object can't contain arrays neither functions.

Play with the code in the Babel Repl.

Volver a posts