Create nested object from query string in Javascript - javascript

I have the following query string:
student.name.firstname=Foo&student.name.lastname=Bar&student.address=My%20Street
How to convert to nested object like this:
{
student:{
name:{
firstname: "Foo",
lastname: "Bar"
},
address: "My Street"
}
}
I have tried the following code but something is wrong:
function convertQueryToMap(query) {
var params = {};
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
var subpairs;
if (pair[0].includes('.')) {
subpairs = pair[0].split('.');
var object = {};
subpairs.reduce(function(o, s, i) {
if (i === subpairs.length-1) {
return o[s] = decodeURIComponent(pair[1]);
} else {
return o[s] = {};
}
}, object);
}
}
return params;
}
Do you know a solution?

You can use reduce method to create nested structure and split method to split the query first on parts based on & and also to get key and value from each part.
const query = 'student.name.firstname=Foo&student.name.lastname=Bar&student.address=My%20Street'
const toObject = string => {
return string.split('&').reduce((r, s) => {
const [key, val] = s.split('=');
key.split('.').reduce((a, e, i, ar) => {
return a[e] || (a[e] = (ar[i + 1] ? {} : val.replace(/%20/g, ' ')))
}, r);
return r;
}, {})
}
const result = toObject(query);
console.log(result)

You could decode the string, split for parts, then for keys and value and assign the value to the nested object.
function setValue(object, keys, value) {
var last = keys.pop();
keys.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
}
var string = 'student.name.firstname=Foo&student.name.lastname=Bar&user.address=My%20Street',
result = {};
decodeURI(string).split('&').forEach(s => {
var [key, value] = s.split('=');
setValue(result, key.split('.'), value);
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

best way to convert from string to javascript object

I have this string:
periodRows.soccer.on:1,periodRows.soccer.periods:1,periodRows.soccer.prematchPeriods=1,periodRows.soccer.label:1st Half
What is the best way to convert it to this object?
periodRows: {
soccer: {
on: 1,
periods: 1,
prematchPeriods: 1,
label: '1st Half',
}
}
Note that I do not control the string, therefore I can not change it.
Thank you
Functionally, a bit shorter.
const f = (obj, keyPath, value) => {
if (keyPath.length === 0) {
return Number.isNaN(Number(value)) ? value : Number(value);
}
const key = keyPath[0];
if (!obj[key]) {
obj[key] = {};
}
obj[key] = f(obj[key], keyPath.slice(1), value);
return obj;
};
const str = "periodRows.soccer.on:1,periodRows.soccer.periods:1,periodRows.soccer.prematchPeriods=1,periodRows.soccer.label:1st Half";
str.split(",")
.map(token => token.split(/[:=]/))
.map(record => ({keyPath: record[0].split("."), value: record[1]}))
.reduce((obj, item) => f(obj, item.keyPath, item.value), {});
Improved recursive solution
const rec = (tokens, index, target) => {
const prop = tokens[index];
if (tokens.length - 1 === index) {
const [lastProp, value] = prop.split(/[:=]/);
target[lastProp] = value;
return target[lastProp];
}
if (prop && !target[prop]) {
target[prop] = {};
}
return target[prop];
}
"periodRows.soccer.on:1,periodRows.soccer.periods:1,periodRows.soccer.prematchPeriods=1,periodRows.soccer.label:1st Half".split(',').reduce((acc, val) => {
const tokens = val.split('.');
let target = acc;
for (let i = 0; i < tokens.length; i++) {
target = rec(tokens, i, target);
}
return acc;
}, {});
By default JS cannot recognize numbers inside of strings. You have to cast it explicitly. You can update code of rec function with this piece.
if (tokens.length - 1 === index) {
const [lastProp, stringValue] = prop.split(/[:=]/);
const parsedValue = +stringValue;
const value = Number.isNaN(parsedValue) ? stringValue: parsedValue;
target[lastProp] = value;
return target[lastProp];
}
The string format you have above is in bad format, the only way to convert it is by first converting it into a string in json-like format, something like the following (notice a json-like string should always be enclosed by {}):
var periodRows = '{"soccer":{"on":1,"periods":1,"prematchPeriods":1,"label":"1st Half"}}';
Then you'd be able to perform the conversion:
//String to Json
const str = JSON.parse(periodRows);
console.log (str);
//Json to string
var periodRows = {
soccer: {
on: 1,
periods: 1,
prematchPeriods: 1,
label: '1st Half',
}
}
var myStr = JSON.stringify(periodRows);
console.log (myStr);

creating a object from array of strings

My input is like
var resources = ["user-john","user-doe", "students-Milan"];
I am trying to get an output as an object like below,
{
user: ["john", "doe"],
students: ["Milan"]
}
What am i doing wrong
var resources = ["user-john","user-doe", "students-Milan"];
let tempObj = {}
resources.forEach(o => {
let tempArr = o.split("-");
if(tempObj[tempArr[0]]){
tempObj[tempArr[0]] = [...tempArr[1], tempArr[1]]
}else{
tempObj[tempArr[0]] = [tempArr[1]]
}
})
console.log(tempObj)
You could deconstructure the splitted string and build an array as value.
var resources = ["user-john", "user-doe", "students-Milan"],
result = resources.reduce(
(r, s) =>
((key, value) => Object.assign(r, { [key]: [].concat(r[key] || [], value) }))
(...s.split('-')),
{}
);
console.log(result);
You could use reduce method here with an object as a accumulator value.
var data = ["user-john", "user-doe", "students-Milan"];
var result = data.reduce((r, e) => {
let [key, value] = e.split('-');
r[key] = (r[key] || []).concat(value)
return r;
}, {})
console.log(result)
A clean, modern solution:
var resources = ["user-john","user-doe", "students-Milan"];
const output = {}
resources.forEach(item => {
const [key, value] = item.split('-')
output[key] = [...output[key] || [], value]
})
console.log(output)
Here in this part you actually need to :
resources.forEach(o => {
let tempArr = o.split("-");
if(tempObj[tempArr[0]]){
tempObj[tempArr[0]] = [...tempObj[tempArr[0]], tempArr[1]];
}else{
tempObj[tempArr[0]] = [tempArr[1]]
}
})
var resources = ["user-john","user-doe", "students-Milan"];
var tmp = {};
resources.forEach(function(e){
var a = e.split("-");
if(typeof tmp[a[0]] == "undefined"){
tmp[a[0]] = [];
tmp[a[0]].push(a[1]);
}else{
tmp[a[0]].push(a[1]);
}
});
console.log(tmp);
You can use .push method instead [...tempArr[1], tempArr[1]]
var resources = ["user-john","user-doe", "students-Milan"];
let tempObj = {}
resources.forEach(o => {
let tempArr = o.split("-");
if(tempObj[tempArr[0]]){
tempObj[tempArr[0]].push(tempArr[1])
}else{
tempObj[tempArr[0]] = [tempArr[1]]
}
})
console.log(tempObj)
Or you can use the spread syntax on the last state of your array like [...tempObj[tempArr[0]], tempArr[1]] instead [...tempArr[1], tempArr[1]]

Rename duplicates in an array JavaScript

I am asking for help to solve the problem renaming the strings in array that looks like this:
["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"]
After the function execution it should look as follows:
["a(1)","a(6)","a","a(2)","a(3)","a(4)","a(5)","a(7)","a(8)","a(9)","a(10)","a(11)"]
Empty array and array free of duplicates should left untouched.
My idea is to populate an empty object with key/value pairs and then just push them to a new array:
function renameFiles(arr){
var itemsObj = {};
var count = 1;
for (var i = 0; i < arr.length; i++){
itemsObj[arr[i]] = count;
// if the key present, rename the array item and add it to the
// itemsObj
if (arr[i] in itemsObj){
itemsObj[arr[i] + '(' + (i - (i - 1)) + ')']
}
}
console.log(itemsObj)
// once the itmesObj is set, run the loop and push the keys to the
// array
return arr;
}
var array = ["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"]
renameFiles(array);
The problem is that the itemsObj is not get populated with duplicates keys. There should be some other method that can handle this task. I am a beginner and probably not aware of that method.
You're almost there. You'd keep a count, and check for duplicates, and then do another check for duplicates with parentheses, and update the count appropriately
function renameFiles(arr){
var count = {};
arr.forEach(function(x,i) {
if ( arr.indexOf(x) !== i ) {
var c = x in count ? count[x] = count[x] + 1 : count[x] = 1;
var j = c + 1;
var k = x + '(' + j + ')';
while( arr.indexOf(k) !== -1 ) k = x + '(' + (++j) + ')';
arr[i] = k;
}
});
return arr;
}
var res = renameFiles(["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"]);
console.log(res)
.as-console-wrapper {top:0; max-height:100%!important}
You were on the right track.
Another solution,
(function(){
var renameFiles = function(arr){
var counts = {}
for(var i=0;i<arr.length;i++){
if(!counts[arr[i]])
counts[arr[i]]=0;
counts[arr[i]]++;
}
arr = [];
for(var name in counts){
for(var i=0;i<counts[name];i++){
arr.push(name+(i===0?'':'('+i+')'));
}
}
return arr;
}
var array = ["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"];
console.log(renameFiles(array))
})();
var arr = ["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"]
function renameFiles(arr){
var dup_count, new_name;
arr.forEach((item, index) => {
dup_count = arr.filter(x => x == item).length;
if (dup_count > 1) {
for(n = 0; n < dup_count;){
do {
new_name = `${item}(${n+=1})`;
} while (arr.includes(new_name));
arr[arr.indexOf(item)] = new_name;
}
}
});
return arr
}
> renameFiles(arr)
< (12) ["a(1)", "a(6)", "a(2)", "a(3)", "a(4)", "a(5)", "a(7)", "a(8)", "a(9)", "a(10)", "a(11)", "a"]
To achieve the same result without mutation you can use the following code. Whilst it is much more code a majority of the functions I have included can be replaced with functions included with Ramda or another FP library. I find the following more readable, however that is simple a matter of preference.
Working sandbox here.
const incrementingList = (n) => [...Array(n).keys()];
const formatStr = (key) => ifElse(equals(0))(always(key))(concat(key));
const incValues = (obj) =>
flatMap((key) => map(formatStr(key))(incrementingList(obj[key])))(keys(obj));
const incOrInit = (record, key) =>
isNil(record[key])
? assoc(key)(1)(record)
: assoc(key)(inc(record[key]))(record);
const generateCounts = reduce({})(incOrInit);
const renameList = compose(incValues, generateCounts);
const list = ["a", "b", "b", "b", "a", "a", "c", "c", "c", "d"];
const result = renameList(list);
console.log(result); // => ["a", "a1", "a2", "b", "b1", "b2", "c", "c1", "c2", "d"]
// THESE FUNCTIONS CAN BE REPLACE WITH RAMDA \\
function keys(obj) {
return Object.keys(obj);
}
function ifElse(cond) {
return function ifTrueFn(trueFn) {
return function ifFalseFn(falseFn) {
return function passValue(value) {
return cond(value) ? trueFn(value) : falseFn(value);
};
};
};
}
function always(value) {
return function alwaysInner() {
return value;
};
}
function concat(a) {
return function inner(b) {
return a.concat(b);
};
}
function equals(a) {
return function equalsInner(b) {
return a === b;
};
}
function compose2(fn1, fn2) {
return function passArg(...args) {
return fn1(fn2(...args));
};
}
function compose(...fns) {
return fns.reduce(compose2);
}
function flatMap(mapFn) {
return function inner(list) {
return list.flatMap(mapFn);
};
}
function map(mapFn) {
return function passList(list) {
return list.map(mapFn);
};
}
function reduce(init) {
return function reducer(reducer) {
return function data(data) {
return data.reduce(reducer, init);
};
};
}
function assoc(key) {
return function assocValue(value) {
return function assocObject(obj) {
return { ...obj, [key]: value };
};
};
}
function inc(n) {
return n + 1;
}
function isNil(value) {
return value == null;
}

Generate a nested object structure by string key/path

I want to make a function called createAssociativeArray which will recive two parameters: string and object, like this:
function createAssociativeArray(string, object) {
//...
}
The last item of string should get the object data. See an use/return example:
createAssociativeArray('key1.key2.key3', {
data1: 1,
data2: 2,
data3: 3
});
// key1: {
// key2: {
// key3: {
// data1: 1,
// data2: 2,
// data3: 3
// }
// }
// }
What's the most simple and robust method to do it?
Use eval isn't is a possibility.
What I was tried:
function createAssociativeArray(string, object) {
string = string.split('.');
return string.reduce(function(_object, _target, i) {
_object[_target] = (i + 1 === string.length ? object : {});
return _object;
}, {});
}
It didn't produced the expected result because the object is reseted to {}.
[JSFiddle]
Here's what I came up with:
function createAssociativeArray(string, object) {
var parts = string.split('.');
var last = parts[parts.length - 1];
var tree = {};
var node = parts.slice(0, -1).reduce(function (memo, current) {
return (memo[current] = {});
}, tree);
node[last] = object;
return tree;
}
I was curious to see if I could make a recursive solution, so here it is:
function createAssociativeArray(string, object) {
if (string === "") return object;
var stringarr = string.split('.');
var laststr = stringarr.pop();
var newobj = {};
newobj[laststr] = object;
return createAssociativeArray(stringarr.join("."), newobj);
}
Working JSFiddle demo: https://jsfiddle.net/pt352dxg/
Possible implementation:
Working demo
function createChain(keys, value) {
var obj = {};
var target = obj;
keys = keys.split('.');
keys.forEach(function(key, index) {
target = target[key] = index === keys.length - 1 ? value : {};
});
target = value;
return obj;
}
This function actually can accept an optional existing Object ({k:2, kk: 3, key1: 4}) and merge that with given json path. e.g. Try on chrome debugger console:
JSON.stringify(createAssociativeArray('key1.key2.key3', { data1: 1, data2: 2, data3: 3}, {k:2,kk:3, key1:{}}))
will print this:
"{"k":2,"kk":3,"key1":{"key2":{"key3":{"data1":1,"data2":2,"data3":3}}}}"
..
function createAssociativeArray(key, value, data) {
if(!finalData && data)
finalData = data;
var finalData;
if (!data)
data = finalData = {};
var keys = key.split('.');
if (keys.length < 2) {
data[keys[0]] = value;
} else {
if (!data[keys[0]])
data[keys[0]] = {};
data = data[keys.shift()];
createAssociativeArray(keys.join("."),value,data);
}
return finalData;
};
You were pretty close in your original attempt.
function createAssociativeArray(string, object) {
return string.split('.').reverse().reduce(function (inner, key) {
var outer = {};
outer[key] = inner;
return outer;
}, object);
}
http://jsfiddle.net/xewoa06t/
This worked for me:
function createAssociativeArray(string, object){
var array = string.split('.');
var aArray = {};
if(array.length > 1){
aArray[array[array.length - 1]] = object;
array.splice(array.length - 1, 1);
createAssociativeArray(array.join('.'), aArray)
}else{
aArray[array[array.length - 1]] = object;
return aArray
}
};
createAssociativeArray('key1.key2.key3', {data1: 1, data2: 2, data3: 3});
Basically, builds object from ground up, starting with the original object, then wrapping the 'layers' around it recursively
Nice case for a recursive function!
function createAssociativeArray(string, object) {
if (string.split('.').length == 1) {
var outObj = {};
outObj[string] = object;
return outObj;
} else {
var outObj = {};
outObj[string.split('.')[0]] = createAssociativeArray(string.split('.').slice(1).join('.'), object);
return outObj;
}
}
It's easier with a simple loop, the key point is doing in reverse (like #JustcallmeDrago)
function createAssociativeArray(keys, data)
{
var temp, keyPart
for(keys = keys.split('.'); keys.length; data = temp)
{
keyPart = keys.pop()
temp = {}
temp[keyPart] = data
}
return data
}
// TEST
x = createAssociativeArray("key1.key2.key3", { data1: "value1", data2: "value2" })
document.write('<pre>'+x+'\n'+x.key1 +'\n'
+x.key1.key2 + '\n'
+x.key1.key2.key3 +'\n'
+x.key1.key2.key3.data1 +'\n'
+x.key1.key2.key3.data2 +'</pre>')
Since no one have proviced a while-loop solution:
function namespace(path, context) {
var obj = context;
var s = path.split('.');
var p;
while (s.length) {
p = s.shift();
obj = obj[p] || (obj[p] = {});
}
return context;
}
ES6 one liner
(str, obj) => str.split('.').reverse().reduce((inner, key) => ({[key]: inner}), obj);

How to convert square bracket object keys from URL location into nested object in Javascript?

With:
var obj = { "object[foo][bar][ya]": 100 };
How can I create:
var obj = { object: { foo: { bar: { ya: 100 }}}};
Manual approach
Split the given string with bracket, then iterate through the resultant tokens to make the nested object:
Given
var obj = { "object[foo][bar][ya]": 100 };
Split them so we get
var tokens = Object.keys(obj)[0]
.split('[')
.map(function(s){return s.replace(']','')});
// tokens = [ 'object', 'foo', 'bar', 'ya' ]
Then make the nested object, inside out
var result = {};
tokens.reverse().forEach(function(key){
if (Object.keys(result).length==0){
result[key] = obj[Object.keys(obj)[0]]; // inner-most key-value
}
else{
var temp = {};
temp[key] = result;
result = temp;
}
});
Result
{"object":{"foo":{"bar":{"ya":100}}}}
Their is no native things in javascript fr parsing nested object in querystring.
You can use http://medialize.github.io/URI.js/ which is pretty damn good at the job.
console.log(URI.parseQuery("?&foo=bar&&foo=bar&foo=baz&"));
If you don't want to import the full library, this is just the part for querystring parsing (full credit to https://github.com/medialize/URI.js):
var URI = {
decodeQuery: function(string, escapeQuerySpace) {
string += '';
try {
return decodeURIComponent(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
} catch(e) {
// we're not going to mess with weird encodings,
// give up and return the undecoded original string
// see https://github.com/medialize/URI.js/issues/87
// see https://github.com/medialize/URI.js/issues/92
return string;
}
},
parseQuery: function(string, escapeQuerySpace) {
if (!string) {
return {};
}
// throw out the funky business - "?"[name"="value"&"]+
string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
if (!string) {
return {};
}
var items = {};
var splits = string.split('&');
var length = splits.length;
var v, name, value;
for (var i = 0; i < length; i++) {
v = splits[i].split('=');
name = URI.decodeQuery(v.shift(), escapeQuerySpace);
// no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;
if (Object.prototype.hasOwnProperty.call(items, name)) {
if (typeof items[name] === 'string') {
items[name] = [items[name]];
}
items[name].push(value);
} else {
items[name] = value;
}
}
return items;
}
};
You could get the parts and build a new object.
const obj = {
"object[foo][bar][ya]": 100,
"object[foo][baz]": 200,
"object[foo][bar][bar]": 50,
"xy": 30
};
let newObj = {};
for (const i in obj) {
let a = i.match(/([^\[\]]+)(\[[^\[\]]+[^\]])*?/g),
p = obj[i];
j = a.length;
while (j--) {
q = {};
q[a[j]] = p;
p = q;
}
// merge object
let k = Object.keys(p)[0],
o = newObj;
while (k in o) {
p = p[k];
o = o[k];
k = Object.keys(p)[0];
}
o[k] = p[k];
}
console.log(newObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's an es6 version. Caution: Hasn't been tested for edge cases.
const keyPattern = /^(\w+)\[(\w+)\](.*)$/;
export function decodeParams(params) {
return Object.keys(params).reduce((result, key) => {
let match = key.match(keyPattern);
if (match && match.length >= 3) {
let [key, nextKey, rest = ''] = match.slice(1);
result[key] = Object.assign(
{},
result[key],
decodeParams({ [nextKey + rest]: params[key] })
);
} else {
result[key] = params[key];
}
return result;
}, {});
}

Categories

Resources