Accessing or creating nested JavaScript objects with string key without eval - javascript

I am looking for a nice solution to access a property by string value, but if the property does not exist it should create it. If the root structure already has defined some parts of the structure, the attributes should not be overwritten, but merged instead.
For example if you have an empty object test and you want to set a deep structure without using eval. e.g.
test = {}
test.foo.name = "Hallo" // <<- foo is an Object
test.foo[3] = "Test" // <<- foo should remain as Object, not as Array
test.foo.data[3].bar = 100 // <<- should not overwrite test.foo.name
I have written a solution that actually works, but it is quite bad code, I guess:
Also available as jsfiddle: https://jsfiddle.net/gvaLzqqf/4/
Object.setValue = function(node, flatKey, value) {
flatKey = flatKey.replace("[", ".");
flatKey = flatKey.replace("]", "");
var parts = flatKey.split(".")
var oldNode = node
parts.forEach(function(key, index) {
if (/^\+?(0|[1-9]\d*)$/.test(key)) {
key = key * 1
if (index > 0) {
var oldValue = parts[index - 1]
if (!Array.isArray(oldNode[oldValue])) {
oldNode[oldValue] = []
node = oldNode[oldValue]
}
}
}
if (node[key] == undefined) {
node[key] = {}
}
oldNode = node
node = node[key]
}); // for each
oldNode[parts[parts.length - 1]] = value
return oldNode[parts[parts.length - 1]]
} // function
var test = {}
Object.setValue(test, "foo.name", "Mr. Foo")
Object.setValue(test, "foo.data[0].bar", 100)
Object.setValue(test, "and.another[2].deep", 20)
console.log("test = " + JSON.stringify(test))
console.log("test.foo.data[0].bar = " + test.foo.data[0].bar)
How ever, is there any better way to achieve this?

You could split the path and reduce the path by walking the given object. If no Object exist, create a new property with the name, or an array. Later assign the value.
function setValue(object, path, value) {
var way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
var test = {};
setValue(test, "foo.name", "Mr. Foo");
setValue(test, "foo.data[0].bar", 100);
setValue(test, "and.another[2].deep", 20);
console.log(test);

I wouldn't reinvent the wheel in this case, and instead use lodash. Specifically the set() function. As per their example:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5

Related

JavaScript: Map Nested Objects that May Exist [duplicate]

I threw some code together to flatten and un-flatten complex/nested JavaScript objects. It works, but it's a bit slow (triggers the 'long script' warning).
For the flattened names I want "." as the delimiter and [INDEX] for arrays.
Examples:
un-flattened | flattened
---------------------------
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1].[0]":2,"[1].[1].[0]":3,"[1].[1].[1]":4,"[1].[2]":5,"[2]":6}
I created a benchmark that ~simulates my use case http://jsfiddle.net/WSzec/
Get a nested object
Flatten it
Look through it and possibly modify it while flattened
Unflatten it back to it's original nested format to be shipped away
I would like faster code: For clarification, code that completes the JSFiddle benchmark (http://jsfiddle.net/WSzec/) significantly faster (~20%+ would be nice) in IE 9+, FF 24+, and Chrome 29+.
Here's the relevant JavaScript code: Current Fastest: http://jsfiddle.net/WSzec/6/
var unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for(var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while(idx >= 0);
cur[prop] = data[p];
}
return result[""];
}
var flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+"."+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
EDIT 1 Modified the above to #Bergi 's implementation which is currently the fastest. As an aside, using ".indexOf" instead of "regex.exec" is around 20% faster in FF but 20% slower in Chrome; so I'll stick with the regex since it's simpler (here's my attempt at using indexOf to replace the regex http://jsfiddle.net/WSzec/2/).
EDIT 2 Building on #Bergi 's idea I managed to created a faster non-regex version (3x faster in FF and ~10% faster in Chrome). http://jsfiddle.net/WSzec/6/ In the this (the current) implementation the rules for key names are simply, keys cannot start with an integer or contain a period.
Example:
{"foo":{"bar":[0]}} => {"foo.bar.0":0}
EDIT 3 Adding #AaditMShah 's inline path parsing approach (rather than String.split) helped to improve the unflatten performance. I'm very happy with the overall performance improvement reached.
The latest jsfiddle and jsperf:
http://jsfiddle.net/WSzec/14/
http://jsperf.com/flatten-un-flatten/4
Here's my much shorter implementation:
Object.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while (m = regex.exec(p)) {
cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
flatten hasn't changed much (and I'm not sure whether you really need those isEmpty cases):
Object.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
Together, they run your benchmark in about the half of the time (Opera 12.16: ~900ms instead of ~ 1900ms, Chrome 29: ~800ms instead of ~1600ms).
Note: This and most other solutions answered here focus on speed and are susceptible to prototype pollution and shold not be used on untrusted objects.
I wrote two functions to flatten and unflatten a JSON object.
Flatten a JSON object:
var flatten = (function (isArray, wrapped) {
return function (table) {
return reduce("", {}, table);
};
function reduce(path, accumulator, table) {
if (isArray(table)) {
var length = table.length;
if (length) {
var index = 0;
while (index < length) {
var property = path + "[" + index + "]", item = table[index++];
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else accumulator[path] = table;
} else {
var empty = true;
if (path) {
for (var property in table) {
var item = table[property], property = path + "." + property, empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else {
for (var property in table) {
var item = table[property], empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
}
if (empty) accumulator[path] = table;
}
return accumulator;
}
}(Array.isArray, Object));
Performance:
It's faster than the current solution in Opera. The current solution is 26% slower in Opera.
It's faster than the current solution in Firefox. The current solution is 9% slower in Firefox.
It's faster than the current solution in Chrome. The current solution is 29% slower in Chrome.
Unflatten a JSON object:
function unflatten(table) {
var result = {};
for (var path in table) {
var cursor = result, length = path.length, property = "", index = 0;
while (index < length) {
var char = path.charAt(index);
if (char === "[") {
var start = index + 1,
end = path.indexOf("]", start),
cursor = cursor[property] = cursor[property] || [],
property = path.slice(start, end),
index = end + 1;
} else {
var cursor = cursor[property] = cursor[property] || {},
start = char === "." ? index + 1 : index,
bracket = path.indexOf("[", start),
dot = path.indexOf(".", start);
if (bracket < 0 && dot < 0) var end = index = length;
else if (bracket < 0) var end = index = dot;
else if (dot < 0) var end = index = bracket;
else var end = index = bracket < dot ? bracket : dot;
var property = path.slice(start, end);
}
}
cursor[property] = table[path];
}
return result[""];
}
Performance:
It's faster than the current solution in Opera. The current solution is 5% slower in Opera.
It's slower than the current solution in Firefox. My solution is 26% slower in Firefox.
It's slower than the current solution in Chrome. My solution is 6% slower in Chrome.
Flatten and unflatten a JSON object:
Overall my solution performs either equally well or even better than the current solution.
Performance:
It's faster than the current solution in Opera. The current solution is 21% slower in Opera.
It's as fast as the current solution in Firefox.
It's faster than the current solution in Firefox. The current solution is 20% slower in Chrome.
Output format:
A flattened object uses the dot notation for object properties and the bracket notation for array indices:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}
In my opinion this format is better than only using the dot notation:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a.0.b.0":"c","a.0.b.1":"d"}
[1,[2,[3,4],5],6] => {"0":1,"1.0":2,"1.1.0":3,"1.1.1":4,"1.2":5,"2":6}
Advantages:
Flattening an object is faster than the current solution.
Flattening and unflattening an object is as fast as or faster than the current solution.
Flattened objects use both the dot notation and the bracket notation for readability.
Disadvantages:
Unflattening an object is slower than the current solution in most (but not all) cases.
The current JSFiddle demo gave the following values as output:
Nested : 132175 : 63
Flattened : 132175 : 564
Nested : 132175 : 54
Flattened : 132175 : 508
My updated JSFiddle demo gave the following values as output:
Nested : 132175 : 59
Flattened : 132175 : 514
Nested : 132175 : 60
Flattened : 132175 : 451
I'm not really sure what that means, so I'll stick with the jsPerf results. After all jsPerf is a performance benchmarking utility. JSFiddle is not.
ES6 version:
const flatten = (obj, path = '') => {
if (!(obj instanceof Object)) return {[path.replace(/\.$/g, '')]:obj};
return Object.keys(obj).reduce((output, key) => {
return obj instanceof Array ?
{...output, ...flatten(obj[key], path + '[' + key + '].')}:
{...output, ...flatten(obj[key], path + key + '.')};
}, {});
}
Example:
console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
3 ½ Years later...
For my own project I wanted to flatten JSON objects in mongoDB dot notation and came up with a simple solution:
/**
* Recursively flattens a JSON object using dot notation.
*
* NOTE: input must be an object as described by JSON spec. Arbitrary
* JS objects (e.g. {a: () => 42}) may result in unexpected output.
* MOREOVER, it removes keys with empty objects/arrays as value (see
* examples bellow).
*
* #example
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e': 3, 'b.1': 4}
* flatten({a: 1, b: [{c: 2, d: {e: 3}}, 4]})
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e.0': true, 'b.0.d.e.1': false, 'b.0.d.e.2.f': 1}
* flatten({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]})
* // return {a: 1}
* flatten({a: 1, b: [], c: {}})
*
* #param obj item to be flattened
* #param {Array.string} [prefix=[]] chain of prefix joined with a dot and prepended to key
* #param {Object} [current={}] result of flatten during the recursion
*
* #see https://docs.mongodb.com/manual/core/document/#dot-notation
*/
function flatten (obj, prefix, current) {
prefix = prefix || []
current = current || {}
// Remember kids, null is also an object!
if (typeof (obj) === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
this.flatten(obj[key], prefix.concat(key), current)
})
} else {
current[prefix.join('.')] = obj
}
return current
}
Features and/or caveats
It only accepts JSON objects. So if you pass something like {a: () => {}} you might not get what you wanted!
It removes empty arrays and objects. So this {a: {}, b: []} is flattened to {}.
Use this library:
npm install flat
Usage (from https://www.npmjs.com/package/flat):
Flatten:
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
Un-flatten:
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
Here's another approach that runs slower (about 1000ms) than the above answer, but has an interesting idea :-)
Instead of iterating through each property chain, it just picks the last property and uses a look-up-table for the rest to store the intermediate results. This look-up-table will be iterated until there are no property chains left and all values reside on uncocatenated properties.
JSON.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)$|\[(\d+)\]$/,
props = Object.keys(data),
result, p;
while(p = props.shift()) {
var m = regex.exec(p),
target;
if (m.index) {
var rest = p.slice(0, m.index);
if (!(rest in data)) {
data[rest] = m[2] ? [] : {};
props.push(rest);
}
target = data[rest];
} else {
target = result || (result = (m[2] ? [] : {}));
}
target[m[2] || m[1]] = data[p];
}
return result;
};
It currently uses the data input parameter for the table, and puts lots of properties on it - a non-destructive version should be possible as well. Maybe a clever lastIndexOf usage performs better than the regex (depends on the regex engine).
See it in action here.
You can use https://github.com/hughsk/flat
Take a nested Javascript object and flatten it, or unflatten an object with delimited keys.
Example from the doc
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
This code recursively flattens out JSON objects.
I included my timing mechanism in the code and it gives me 1ms but I'm not sure if that's the most accurate one.
var new_json = [{
"name": "fatima",
"age": 25,
"neighbour": {
"name": "taqi",
"location": "end of the street",
"property": {
"built in": 1990,
"owned": false,
"years on market": [1990, 1998, 2002, 2013],
"year short listed": [], //means never
}
},
"town": "Mountain View",
"state": "CA"
},
{
"name": "qianru",
"age": 20,
"neighbour": {
"name": "joe",
"location": "opposite to the park",
"property": {
"built in": 2011,
"owned": true,
"years on market": [1996, 2011],
"year short listed": [], //means never
}
},
"town": "Pittsburgh",
"state": "PA"
}]
function flatten(json, flattened, str_key) {
for (var key in json) {
if (json.hasOwnProperty(key)) {
if (json[key] instanceof Object && json[key] != "") {
flatten(json[key], flattened, str_key + "." + key);
} else {
flattened[str_key + "." + key] = json[key];
}
}
}
}
var flattened = {};
console.time('flatten');
flatten(new_json, flattened, "");
console.timeEnd('flatten');
for (var key in flattened){
console.log(key + ": " + flattened[key]);
}
Output:
flatten: 1ms
.0.name: fatima
.0.age: 25
.0.neighbour.name: taqi
.0.neighbour.location: end of the street
.0.neighbour.property.built in: 1990
.0.neighbour.property.owned: false
.0.neighbour.property.years on market.0: 1990
.0.neighbour.property.years on market.1: 1998
.0.neighbour.property.years on market.2: 2002
.0.neighbour.property.years on market.3: 2013
.0.neighbour.property.year short listed:
.0.town: Mountain View
.0.state: CA
.1.name: qianru
.1.age: 20
.1.neighbour.name: joe
.1.neighbour.location: opposite to the park
.1.neighbour.property.built in: 2011
.1.neighbour.property.owned: true
.1.neighbour.property.years on market.0: 1996
.1.neighbour.property.years on market.1: 2011
.1.neighbour.property.year short listed:
.1.town: Pittsburgh
.1.state: PA
Here's mine. It runs in <2ms in Google Apps Script on a sizable object. It uses dashes instead of dots for separators, and it doesn't handle arrays specially like in the asker's question, but this is what I wanted for my use.
function flatten (obj) {
var newObj = {};
for (var key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
var temp = flatten(obj[key])
for (var key2 in temp) {
newObj[key+"-"+key2] = temp[key2];
}
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
Example:
var test = {
a: 1,
b: 2,
c: {
c1: 3.1,
c2: 3.2
},
d: 4,
e: {
e1: 5.1,
e2: 5.2,
e3: {
e3a: 5.31,
e3b: 5.32
},
e4: 5.4
},
f: 6
}
Logger.log("start");
Logger.log(JSON.stringify(flatten(test),null,2));
Logger.log("done");
Example output:
[17-02-08 13:21:05:245 CST] start
[17-02-08 13:21:05:246 CST] {
"a": 1,
"b": 2,
"c-c1": 3.1,
"c-c2": 3.2,
"d": 4,
"e-e1": 5.1,
"e-e2": 5.2,
"e-e3-e3a": 5.31,
"e-e3-e3b": 5.32,
"e-e4": 5.4,
"f": 6
}
[17-02-08 13:21:05:247 CST] done
Object.prototype.flatten = function (obj) {
let ans = {};
let anotherObj = { ...obj };
function performFlatten(anotherObj) {
Object.keys(anotherObj).forEach((key, idx) => {
if (typeof anotherObj[key] !== 'object') {
ans[key] = anotherObj[key];
console.log('ans so far : ', ans);
} else {
console.log(key, { ...anotherObj[key] });
performFlatten(anotherObj[key]);
}
})
}
performFlatten(anotherObj);
return ans;
}
let ans = flatten(obj);
console.log(ans);
I added +/- 10-15% efficiency to the selected answer by minor code refactoring and moving the recursive function outside of the function namespace.
See my question: Are namespaced functions reevaluated on every call? for why this slows nested functions down.
function _flatten (target, obj, path) {
var i, empty;
if (obj.constructor === Object) {
empty = true;
for (i in obj) {
empty = false;
_flatten(target, obj[i], path ? path + '.' + i : i);
}
if (empty && path) {
target[path] = {};
}
}
else if (obj.constructor === Array) {
i = obj.length;
if (i > 0) {
while (i--) {
_flatten(target, obj[i], path + '[' + i + ']');
}
} else {
target[path] = [];
}
}
else {
target[path] = obj;
}
}
function flatten (data) {
var result = {};
_flatten(result, data, null);
return result;
}
See benchmark.
Here's a recursive solution for flatten I put together in PowerShell:
#---helper function for ConvertTo-JhcUtilJsonTable
#
function getNodes {
param (
[Parameter(Mandatory)]
[System.Object]
$job,
[Parameter(Mandatory)]
[System.String]
$path
)
$t = $job.GetType()
$ct = 0
$h = #{}
if ($t.Name -eq 'PSCustomObject') {
foreach ($m in Get-Member -InputObject $job -MemberType NoteProperty) {
getNodes -job $job.($m.Name) -path ($path + '.' + $m.Name)
}
}
elseif ($t.Name -eq 'Object[]') {
foreach ($o in $job) {
getNodes -job $o -path ($path + "[$ct]")
$ct++
}
}
else {
$h[$path] = $job
$h
}
}
#---flattens a JSON document object into a key value table where keys are proper JSON paths corresponding to their value
#
function ConvertTo-JhcUtilJsonTable {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Object[]]
$jsonObj
)
begin {
$rootNode = 'root'
}
process {
foreach ($o in $jsonObj) {
$table = getNodes -job $o -path $rootNode
# $h = #{}
$a = #()
$pat = '^' + $rootNode
foreach ($i in $table) {
foreach ($k in $i.keys) {
# $h[$k -replace $pat, ''] = $i[$k]
$a += New-Object -TypeName psobject -Property #{'Key' = $($k -replace $pat, ''); 'Value' = $i[$k]}
# $h[$k -replace $pat, ''] = $i[$k]
}
}
# $h
$a
}
}
end{}
}
Example:
'{"name": "John","Address": {"house": "1234", "Street": "Boogie Ave"}, "pets": [{"Type": "Dog", "Age": 4, "Toys": ["rubberBall", "rope"]},{"Type": "Cat", "Age": 7, "Toys": ["catNip"]}]}' | ConvertFrom-Json | ConvertTo-JhcUtilJsonTable
Key Value
--- -----
.Address.house 1234
.Address.Street Boogie Ave
.name John
.pets[0].Age 4
.pets[0].Toys[0] rubberBall
.pets[0].Toys[1] rope
.pets[0].Type Dog
.pets[1].Age 7
.pets[1].Toys[0] catNip
.pets[1].Type Cat
I wanted an approach so that I could be able to easily convert my json data into a csv file.
The scenario is: I query data from somewhere and I receive an array of some model, like a bank extract.
This approach below is used to parse each one of these entries.
function jsonFlatter(data, previousKey, obj) {
obj = obj || {}
previousKey = previousKey || ""
Object.keys(data).map(key => {
let newKey = `${previousKey}${previousKey ? "_" : ""}${key}`
let _value = data[key]
let isArray = Array.isArray(_value)
if (typeof _value !== "object" || isArray || _value == null) {
if (isArray) {
_value = JSON.stringify(_value)
} else if (_value == null) {
_value = "null"
}
obj[newKey] = _value
} else if (typeof _value === "object") {
if (!Object.keys(_value).length) {
obj[newKey] = "null"
} else {
return jsonFlatter(_value, newKey, obj)
}
}
})
return obj
}
This way, I can count on the uniformity of the keys and inner keys of my object model, but arrays are simply stringified since I can't rely on their uniformity. Also, empty objects become the string "null", since I still want it's key to appear in the final result.
Usage example:
const test_data = {
a: {
aa: {
aaa: 4354,
aab: 654
},
ab: 123
},
b: 234,
c: {},
d: []
}
console.log('result', jsonFlatter(test_data))
#### output
{
"a_aa_aaa": 4354,
"a_aa_aab": 654,
"a_ab": 123,
"b": 234,
"c": "null",
"d": "[]"
}
try this one:
function getFlattenObject(data, response = {}) {
for (const key in data) {
if (typeof data[key] === 'object' && !Array.isArray(data[key])) {
getFlattenObject(data[key], response);
} else {
response[key] = data[key];
}
}
return response;
}
I'd like to add a new version of flatten case (this is what i needed :)) which, according to my probes with the above jsFiddler, is slightly faster then the currently selected one.
Moreover, me personally see this snippet a bit more readable, which is of course important for multi-developer projects.
function flattenObject(graph) {
let result = {},
item,
key;
function recurr(graph, path) {
if (Array.isArray(graph)) {
graph.forEach(function (itm, idx) {
key = path + '[' + idx + ']';
if (itm && typeof itm === 'object') {
recurr(itm, key);
} else {
result[key] = itm;
}
});
} else {
Reflect.ownKeys(graph).forEach(function (p) {
key = path + '.' + p;
item = graph[p];
if (item && typeof item === 'object') {
recurr(item, key);
} else {
result[key] = item;
}
});
}
}
recurr(graph, '');
return result;
}
Here is some code I wrote to flatten an object I was working with. It creates a new class that takes every nested field and brings it into the first layer. You could modify it to unflatten by remembering the original placement of the keys. It also assumes the keys are unique even across nested objects. Hope it helps.
class JSONFlattener {
ojson = {}
flattenedjson = {}
constructor(original_json) {
this.ojson = original_json
this.flattenedjson = {}
this.flatten()
}
flatten() {
Object.keys(this.ojson).forEach(function(key){
if (this.ojson[key] == null) {
} else if (this.ojson[key].constructor == ({}).constructor) {
this.combine(new JSONFlattener(this.ojson[key]).returnJSON())
} else {
this.flattenedjson[key] = this.ojson[key]
}
}, this)
}
combine(new_json) {
//assumes new_json is a flat array
Object.keys(new_json).forEach(function(key){
if (!this.flattenedjson.hasOwnProperty(key)) {
this.flattenedjson[key] = new_json[key]
} else {
console.log(key+" is a duplicate key")
}
}, this)
}
returnJSON() {
return this.flattenedjson
}
}
console.log(new JSONFlattener(dad_dictionary).returnJSON())
As an example, it converts
nested_json = {
"a": {
"b": {
"c": {
"d": {
"a": 0
}
}
}
},
"z": {
"b":1
},
"d": {
"c": {
"c": 2
}
}
}
into
{ a: 0, b: 1, c: 2 }
You can try out the package jpflat.
It flattens, inflates, resolves promises, flattens arrays, has customizable path creation and customizable value serialization.
The reducers and serializers receive the whole path as an array of it's parts, so more complex operations can be done to the path instead of modifying a single key or changing the delimiter.
Json path is the default, hence "jp"flat.
https://www.npmjs.com/package/jpflat
let flatFoo = await require('jpflat').flatten(foo)

Better way to update an object's value at a variable depth

I am working on some software that reads/writes information in localStorage using a handler. You can find a working example here: http://jsbin.com/wifucugoko/edit?js,console
My problem is with the segment of code below (focusing on the switch statement):
_t.set = function(path, value) { // Update a single value or object
if (~path.indexOf(".")) {
let o = path.split(".")[0],
p = this.get(o),
q = path.split(".").slice(1);
switch (q.length) {
// There has to be a better way to do this...
case 1:
p[q[0]] = value;
break;
case 2:
p[q[0]][q[1]] = value;
break;
case 3:
p[q[0]][q[1]][q[2]] = value;
break;
case 4:
p[q[0]][q[1]][q[2]][q[3]] = value;
break;
case 5:
p[q[0]][q[1]][q[2]][q[3]][q[4]] = value;
break;
case 6:
p[q[0]][q[1]][q[2]][q[3]][q[4]][q[5]] = value;
break;
default:
return "error";
}
b.setItem(o, JSON.stringify(p));
return p;
} else {
b.setItem(path, JSON.stringify(value));
return this.get(path);
}
};
I am not going to be the only one using this codebase, and I am trying to make it easy for others to update any value that could be placed in localStorage. Right now you can update a value by using something like local.set('item.subitem.proeprty', 'value') Though the code above does that, it's ugly and doesn't scale.
How can this method be improved to (1) update a property nested at any depth automatically, instead of writing an infinitely-long switch statement, and (2) not lace a parent object with [object Object] after a value is updated?
This question has nothing to do with my use of localStorage. I originally posted this question in code review, which requires a working contextual example. They closed this question immediately, since part of my problem is the code I provided doesn't work once you start dealing with updating a value nested more than six objects deep. Though I could have continued my switch statement indefinitely, that's exactly what I'm trying to avoid.
With the three examples provided you'll see that setting a value in one place doesn't remove values in other places:
local.set('user.session.timeout', false);
local.set('user.name', {first:'john', last:'doe', mi:'c'});
local.set('user.PIN', 8675309);
All these values, though set at different times, only UPDATE or create a value, they do NOT clear any pre-existing values elsewhere.
As for me the minimal optimization would be following:
if (~path.indexOf(".")) {
let o = path.split(".")[0],
p = this.get(o),
q = path.split(".").slice(1),
dist = p;
q.forEach(function(item, index) {
if (index < q.length - 1) {
dist = dist[item];
} else {
dist[item] = value;
}
});
b.setItem(o, JSON.stringify(p));
return p;
} else {
changed parts:
dist variable is created
hardcoded switch is replaced with foreach
You could try something like this, if the path does not exists, the value is null:
function retreiveValueFromObject(path, object) {
var pathList = path.split(".");
var currentValue = object;
var brokeEarly = false;
for (var i = 0; i < pathList.length; i++) {
if (currentValue[pathList[i]]) {
currentValue = currentValue[pathList[i]];
} else {
brokeEarly = true;
break;
}
}
return {
value: currentValue,
brokeEarly: brokeEarly
};
}
function setValueInObject(path, value, object) {
var nestedObj = retreiveValueFromObject(path, object).value;
var pathList = path.split(".");
var finalKey = pathList[pathList.length - 1];
nestedObj[finalKey] = value;
}
var someObject = {
a: {
c: {
d: "value"
},
z: "c"
},
b: {
f: {
x: "world"
},
g: "hello"
},
};
console.log(retreiveValueFromObject("b.f.x", someObject));
setValueInObject("b.f.y", "newValue", someObject);
console.log(someObject);
What you are looking for is a little bit of recursion, I just implemented the update method.
let localStorageHandler = function() {
let b = window.localStorage,
_t = this;
_t.get = function(a) {
try {
return JSON.parse(b.getItem(a))
} catch (c) {
return b.getItem(a)
}
};
function descendAndUpdate(obj, path, value) {
let current = path[0],
remainingPath = path.slice(1);
// found and update
if (obj.hasOwnProperty(current) && remainingPath.length === 0) {
obj[current] = value;
// found but still not there
} else if (obj.hasOwnProperty(current)) {
return descendAndUpdate(obj[current], remainingPath, value);
}
// if you want do add new properties use:
// obj[current] = value;
// in the else clause
else {
throw('can not update unknown property');
}
}
_t.set = function(path, value) { // Update a single value or object
if (~path.indexOf(".")) {
let o = path.split(".")[0],
p = this.get(o),
q = path.split(".").slice(1);
descendAndUpdate(p, q, value);
console.log(p);
b.setItem(o, JSON.stringify(p));
return p;
} else {
b.setItem(path, JSON.stringify(value));
return this.get(path);
}
};
_t.remove = function(a) { // removes a single object from localstorage
let c = !1;
a = "number" === typeof a ? this.key(a) : a;
a in b && (c = !0, b.removeItem(a));
return c
};
};
let local = new localStorageHandler();
// Create user and session info if it doesn't exist
let blankUser = new Object({
alias: '',
dob: '',
PIN: '',
level: 0,
name: {
first: '',
last: '',
mi:'',
},
session: {
token: '',
timeout: true,
lastChange: Date.now()
}
});
local.remove('user');
// Loads user data into localstorage
if (!local.get('user')) {
local.set('user', blankUser);
}
local.set('user.session.timeout', false);
local.set('user.name', {first:'john', last:'doe', mi:'c'});
local.set('user.PIN', 8675309);
// new property
// local.set('user.sunshine', { 'like': 'always' });
console.log(local.get('user'));
A friend of mine would always prefer stacks over recursion, which would be a second option. Anyway I agree with many of the comments here. You already know your domain model. Unless you have a very good reason for this approach spend more time on serializing and unserializing those objects in the database. I have the impression you would be able to work with your data in a more natural way because the aspect of updating fields in a database would be abstracted away.
I am working on a similar project at the moment. What I am doing is storing the data in something I called a WordMatrix (https://github.com/SamM/Rephrase/blob/master/WordMatrix.js), maybe you could use something like it in your solution.
My project is a WIP but the next step is literally to add support for localStorage. The project itself is a database editor that works with key => value stores. You can view the prototype for it here: (https://samm.github.io/Rephrase/editor.html)
Once I have implemented the localStorage aspect I will update this post.
Your topic reminds me one recent another topic.
Trying to enhance the answer I provided, I propose you these functions:
// Function to get a nested element:
function obj_get_path(obj, path) {
return path.split('.').reduce((accu, val) => accu[val] || 'Not found', obj);
}
// Function to set a nested element:
function obj_set_path(obj, path, value) {
var result = obj;
var paths = path.split('.');
var len = paths.length;
for (var i = 0; i < len - 1; i++) {
result = result[paths[i]] || {};
}
result[paths[len - 1]] = value;
return obj;
}
// Example object
var obj = {
name0: 'A name',
level0: {
name1: 'An other name',
level1: {
level2: {
name3: 'Name to be changed',
text3: 'Some other text'
}
}
}
}
// Use of the function
obj = obj_set_path(obj, 'level0.level1.level2.name3', 'Takit Isy');
obj = obj_set_path(obj, 'level0.level1.level2.new3', 'I’m a new element!');
var obj_level2 = obj_get_path(obj, 'level0.level1.level2');
// Consoling
console.log('Consoling of obj_level2:\n', obj_level2);
console.log('\nConsoling of full obj:\n', obj); // To see that the object is correct
⋅
⋅
⋅
We could also adapt the 2nd function in my above snippet so that it works for both get and set, depending of if "value" is set:
// We could also adapt the second function for both uses:
function obj_path(obj, path, value = null) {
var result = obj;
var paths = path.split('.');
var len = paths.length;
for (var i = 0; i < len - 1; i++) {
result = result[paths[i]] || {};
}
// Return result if there is no set value
if (value === null) return result[paths[len - 1]];
// Set value and return
result[paths[len - 1]] = value;
return obj;
}
// Example object
var obj = {
name0: 'A name',
level0: {
name1: 'An other name',
level1: {
level2: {
name3: 'Name to be changed',
text3: 'Some other text'
}
}
}
}
// Use of the function
obj = obj_path(obj, 'level0.level1.level2.name3', 'Takit Isy');
obj = obj_path(obj, 'level0.level1.level2.new3', 'I’m a new element!');
var obj_level2 = obj_path(obj, 'level0.level1.level2');
// Consoling
console.log('Consoling of obj_level2:\n', obj_level2);
console.log('\nConsoling of full obj:\n', obj); // To see that the object is correct
Hope it helps.
How about:
function parse(str) {
var arr = str.split('.');
return function(obj) {
return arr.reduce((o, i) => o[i], obj);
}
}
let foo = {
a: {
b: {
c: {
bar: 0
}
}
}
}
let c = parse('a.b.c')(foo);
console.log(c.bar);
c['bar'] = 1;
console.log(foo);

convert serialized form to individual post items [duplicate]

This question already has answers here:
How can I get query string values in JavaScript?
(73 answers)
Closed 1 year ago.
I have a string like this:
abc=foo&def=%5Basf%5D&xyz=5
How can I convert it into a JavaScript object like this?
{
abc: 'foo',
def: '[asf]',
xyz: 5
}
In the year 2021... Please consider this obsolete.
Edit
This edit improves and explains the answer based on the comments.
var search = location.search.substring(1);
JSON.parse('{"' + decodeURI(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}')
Example
Parse abc=foo&def=%5Basf%5D&xyz=5 in five steps:
decodeURI: abc=foo&def=[asf]&xyz=5
Escape quotes: same, as there are no quotes
Replace &: abc=foo","def=[asf]","xyz=5
Replace =: abc":"foo","def":"[asf]","xyz":"5
Suround with curlies and quotes: {"abc":"foo","def":"[asf]","xyz":"5"}
which is legal JSON.
An improved solution allows for more characters in the search string. It uses a reviver function for URI decoding:
var search = location.search.substring(1);
JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g,'":"') + '"}', function(key, value) { return key===""?value:decodeURIComponent(value) })
Example
search = "abc=foo&def=%5Basf%5D&xyz=5&foo=b%3Dar";
gives
Object {abc: "foo", def: "[asf]", xyz: "5", foo: "b=ar"}
Original answer
A one-liner:
JSON.parse('{"' + decodeURI("abc=foo&def=%5Basf%5D&xyz=5".replace(/&/g, "\",\"").replace(/=/g,"\":\"")) + '"}')
2022 ES6/7/8 and on approach
Starting ES6 and on, Javascript offers several constructs in order to create a performant solution for this issue.
This includes using URLSearchParams and iterators
let params = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5');
params.get("abc"); // "foo"
Should your use case requires you to actually convert it to object, you can implement the following function:
function paramsToObject(entries) {
const result = {}
for(const [key, value] of entries) { // each 'entry' is a [key, value] tupple
result[key] = value;
}
return result;
}
Basic Demo
const urlParams = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5');
const entries = urlParams.entries(); //returns an iterator of decoded [key,value] tuples
const params = paramsToObject(entries); //{abc:"foo",def:"[asf]",xyz:"5"}
Using Object.fromEntries and spread
We can use Object.fromEntries, replacing paramsToObject with Object.fromEntries(entries).
The value pairs to iterate over are the list name-value pairs with the
key being the name and the value being the value.
Since URLParams, returns an iterable object, using the spread operator instead of calling .entries will also yield entries per its spec:
const urlParams = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5');
const params = Object.fromEntries(urlParams); // {abc: "foo", def: "[asf]", xyz: "5"}
Note: All values are automatically strings as per the URLSearchParams spec
Multiple same keys
As #siipe pointed out, strings containing multiple same-key values will be coerced into the last available value: foo=first_value&foo=second_value will in essence become: {foo: "second_value"}.
As per this answer: https://stackoverflow.com/a/1746566/1194694 there's no spec for deciding what to do with it and each framework can behave differently.
A common use case will be to join the two same values into an array, making the output object into:
{foo: ["first_value", "second_value"]}
This can be achieved with the following code:
const groupParamsByKey = (params) => [...params.entries()].reduce((acc, tuple) => {
// getting the key and value from each tuple
const [key, val] = tuple;
if(acc.hasOwnProperty(key)) {
// if the current key is already an array, we'll add the value to it
if(Array.isArray(acc[key])) {
acc[key] = [...acc[key], val]
} else {
// if it's not an array, but contains a value, we'll convert it into an array
// and add the current value to it
acc[key] = [acc[key], val];
}
} else {
// plain assignment if no special case is present
acc[key] = val;
}
return acc;
}, {});
const params = new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5&def=dude');
const output = groupParamsByKey(params) // {abc: "foo", def: ["[asf]", "dude"], xyz: 5}
One liner. Clean and simple.
const params = Object.fromEntries(new URLSearchParams(location.search));
For your specific case, it would be:
const str = 'abc=foo&def=%5Basf%5D&xyz=5';
const params = Object.fromEntries(new URLSearchParams(str));
console.log(params);
2023 One-Liner Approach
For the general case where you want to parse query params to an object:
Object.fromEntries(new URLSearchParams(location.search));
For your specific case:
Object.fromEntries(new URLSearchParams('abc=foo&def=%5Basf%5D&xyz=5'));
Split on & to get name/value pairs, then split each pair on =. Here's an example:
var str = "abc=foo&def=%5Basf%5D&xy%5Bz=5"
var obj = str.split("&").reduce(function(prev, curr, i, arr) {
var p = curr.split("=");
prev[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
return prev;
}, {});
Another approach, using regular expressions:
var obj = {};
str.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
obj[decodeURIComponent(key)] = decodeURIComponent(value);
});
This is adapted from John Resig's "Search and Don’t Replace".
The proposed solutions I found so far do not cover more complex scenarios.
I needed to convert a query string like
https://random.url.com?Target=Offer&Method=findAll&filters%5Bhas_goals_enabled%5D%5BTRUE%5D=1&filters%5Bstatus%5D=active&fields%5B%5D=id&fields%5B%5D=name&fields%5B%5D=default_goal_name
into an object like:
{
"Target": "Offer",
"Method": "findAll",
"fields": [
"id",
"name",
"default_goal_name"
],
"filters": {
"has_goals_enabled": {
"TRUE": "1"
},
"status": "active"
}
}
OR:
https://random.url.com?Target=Report&Method=getStats&fields%5B%5D=Offer.name&fields%5B%5D=Advertiser.company&fields%5B%5D=Stat.clicks&fields%5B%5D=Stat.conversions&fields%5B%5D=Stat.cpa&fields%5B%5D=Stat.payout&fields%5B%5D=Stat.date&fields%5B%5D=Stat.offer_id&fields%5B%5D=Affiliate.company&groups%5B%5D=Stat.offer_id&groups%5B%5D=Stat.date&filters%5BStat.affiliate_id%5D%5Bconditional%5D=EQUAL_TO&filters%5BStat.affiliate_id%5D%5Bvalues%5D=1831&limit=9999
INTO:
{
"Target": "Report",
"Method": "getStats",
"fields": [
"Offer.name",
"Advertiser.company",
"Stat.clicks",
"Stat.conversions",
"Stat.cpa",
"Stat.payout",
"Stat.date",
"Stat.offer_id",
"Affiliate.company"
],
"groups": [
"Stat.offer_id",
"Stat.date"
],
"limit": "9999",
"filters": {
"Stat.affiliate_id": {
"conditional": "EQUAL_TO",
"values": "1831"
}
}
}
I compiled and adapted multiple solutions into one that actually works:
CODE:
var getParamsAsObject = function (query) {
query = query.substring(query.indexOf('?') + 1);
var re = /([^&=]+)=?([^&]*)/g;
var decodeRE = /\+/g;
var decode = function (str) {
return decodeURIComponent(str.replace(decodeRE, " "));
};
var params = {}, e;
while (e = re.exec(query)) {
var k = decode(e[1]), v = decode(e[2]);
if (k.substring(k.length - 2) === '[]') {
k = k.substring(0, k.length - 2);
(params[k] || (params[k] = [])).push(v);
}
else params[k] = v;
}
var assign = function (obj, keyPath, value) {
var lastKeyIndex = keyPath.length - 1;
for (var i = 0; i < lastKeyIndex; ++i) {
var key = keyPath[i];
if (!(key in obj))
obj[key] = {}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
for (var prop in params) {
var structure = prop.split('[');
if (structure.length > 1) {
var levels = [];
structure.forEach(function (item, i) {
var key = item.replace(/[?[\]\\ ]/g, '');
levels.push(key);
});
assign(params, levels, params[prop]);
delete(params[prop]);
}
}
return params;
};
A concise solution:
location.search
.slice(1)
.split('&')
.map(p => p.split('='))
.reduce((obj, pair) => {
const [key, value] = pair.map(decodeURIComponent);
obj[key] = value;
return obj;
}, {});
This is the simple version, obviously you'll want to add some error checking:
var obj = {};
var pairs = queryString.split('&');
for(i in pairs){
var split = pairs[i].split('=');
obj[decodeURIComponent(split[0])] = decodeURIComponent(split[1]);
}
For Node JS, you can use the Node JS API querystring:
const querystring = require('querystring');
querystring.parse('abc=foo&def=%5Basf%5D&xyz=5&foo=b%3Dar');
// returns the object
Documentation: https://nodejs.org/api/querystring.html
I found $.String.deparam the most complete pre built solution (can do nested objects etc.). Check out the documentation.
Another solution based on the latest standard of URLSearchParams (https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
function getQueryParamsObject() {
const searchParams = new URLSearchParams(location.search.slice(1));
return searchParams
? _.fromPairs(Array.from(searchParams.entries()))
: {};
}
Please note that this solution is making use of
Array.from (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)
and _.fromPairs (https://lodash.com/docs#fromPairs) of lodash for the sake of simplicity.
It should be easy to create a more compatible solution since you have access to searchParams.entries() iterator.
I had the same problem, tried the solutions here, but none of them really worked, since I had arrays in the URL parameters, like this:
?param[]=5&param[]=8&othr_param=abc&param[]=string
So I ended up writing my own JS function, which makes an array out of the param in URI:
/**
* Creates an object from URL encoded data
*/
var createObjFromURI = function() {
var uri = decodeURI(location.search.substr(1));
var chunks = uri.split('&');
var params = Object();
for (var i=0; i < chunks.length ; i++) {
var chunk = chunks[i].split('=');
if(chunk[0].search("\\[\\]") !== -1) {
if( typeof params[chunk[0]] === 'undefined' ) {
params[chunk[0]] = [chunk[1]];
} else {
params[chunk[0]].push(chunk[1]);
}
} else {
params[chunk[0]] = chunk[1];
}
}
return params;
}
One of the simplest way to do this using URLSearchParam interface.
Below is the working code snippet:
let paramObj={},
querystring=window.location.search,
searchParams = new URLSearchParams(querystring);
//*** :loop to add key and values to the param object.
searchParams.forEach(function(value, key) {
paramObj[key] = value;
});
There is quite simple and incorrect answer with ES6:
console.log(
Object.fromEntries(new URLSearchParams(`abc=foo&def=%5Basf%5D&xyz=5`))
);
But this one line code do not cover multiple same keys, you have to use something more complicated:
function parseParams(params) {
const output = [];
const searchParams = new URLSearchParams(params);
// Set will return only unique keys()
new Set([...searchParams.keys()])
.forEach(key => {
output[key] = searchParams.getAll(key).length > 1 ?
searchParams.getAll(key) : // get multiple values
searchParams.get(key); // get single value
});
return output;
}
console.log(
parseParams('abc=foo&cars=Ford&cars=BMW&cars=Skoda&cars=Mercedes')
)
Code will generate follow structure:
[
abc: "foo"
cars: ["Ford", "BMW", "Skoda", "Mercedes"]
]
Using ES6, URL API and URLSearchParams API.
function objectifyQueryString(url) {
let _url = new URL(url);
let _params = new URLSearchParams(_url.search);
let query = Array.from(_params.keys()).reduce((sum, value)=>{
return Object.assign({[value]: _params.get(value)}, sum);
}, {});
return query;
}
ES6 one liner (if we can call it that way seeing the long line)
[...new URLSearchParams(location.search).entries()].reduce((prev, [key,val]) => {prev[key] = val; return prev}, {})
One simple answer with build in native Node module.(No third party npm modules)
The querystring module provides utilities for parsing and formatting URL query strings. It can be accessed using:
const querystring = require('querystring');
const body = "abc=foo&def=%5Basf%5D&xyz=5"
const parseJSON = querystring.parse(body);
console.log(parseJSON);
Pretty easy using the URLSearchParams JavaScript Web API,
var paramsString = "abc=foo&def=%5Basf%5D&xyz=5";
//returns an iterator object
var searchParams = new URLSearchParams(paramsString);
//Usage
for (let p of searchParams) {
console.log(p);
}
//Get the query strings
console.log(searchParams.toString());
//You can also pass in objects
var paramsObject = {abc:"forum",def:"%5Basf%5D",xyz:"5"}
//returns an iterator object
var searchParams = new URLSearchParams(paramsObject);
//Usage
for (let p of searchParams) {
console.log(p);
}
//Get the query strings
console.log(searchParams.toString());
##Useful Links
URLSearchParams - Web APIs | MDN
Easy URL Manipulation with URLSearchParams | Web
| Google Developers
NOTE: Not Supported in IE
There is no native solution that I'm aware of. Dojo has a built-in unserialization method if you use that framework by chance.
Otherwise you can implement it yourself rather simply:
function unserialize(str) {
str = decodeURIComponent(str);
var chunks = str.split('&'),
obj = {};
for(var c=0; c < chunks.length; c++) {
var split = chunks[c].split('=', 2);
obj[split[0]] = split[1];
}
return obj;
}
edit: added decodeURIComponent()
/**
* Parses and builds Object of URL query string.
* #param {string} query The URL query string.
* #return {!Object<string, string>}
*/
function parseQueryString(query) {
if (!query) {
return {};
}
return (/^[?#]/.test(query) ? query.slice(1) : query)
.split('&')
.reduce((params, param) => {
const item = param.split('=');
const key = decodeURIComponent(item[0] || '');
const value = decodeURIComponent(item[1] || '');
if (key) {
params[key] = value;
}
return params;
}, {});
}
console.log(parseQueryString('?v=MFa9pvnVe0w&ku=user&from=89&aw=1'))
see log
There's a lightweight library called YouAreI.js that's tested and makes this really easy.
YouAreI = require('YouAreI')
uri = new YouAreI('http://user:pass#www.example.com:3000/a/b/c?d=dad&e=1&f=12.3#fragment');
uri.query_get() => { d: 'dad', e: '1', f: '12.3' }
If you are using URI.js, you can use:
https://medialize.github.io/URI.js/docs.html#static-parseQuery
var result = URI.parseQuery("?foo=bar&hello=world&hello=mars&bam=&yup");
result === {
foo: "bar",
hello: ["world", "mars"],
bam: "",
yup: null
};
console.log(decodeURI('abc=foo&def=%5Basf%5D&xyz=5')
.split('&')
.reduce((result, current) => {
const [key, value] = current.split('=');
result[key] = value;
return result
}, {}))
This seems to be the best solution as it takes multiple parameters of the same name into consideration.
function paramsToJSON(str) {
var pairs = str.split('&');
var result = {};
pairs.forEach(function(pair) {
pair = pair.split('=');
var name = pair[0]
var value = pair[1]
if( name.length )
if (result[name] !== undefined) {
if (!result[name].push) {
result[name] = [result[name]];
}
result[name].push(value || '');
} else {
result[name] = value || '';
}
});
return( result );
}
something
paramsToJSON("x=1&x=2&x=3&y=blah");
console yields => {x: Array[3], y: "blah"} where x is an array as is proper JSON
I later decided to convert it to a jQuery plugin too...
$.fn.serializeURLParams = function() {
var result = {};
if( !this.is("a") || this.attr("href").indexOf("?") == -1 )
return( result );
var pairs = this.attr("href").split("?")[1].split('&');
pairs.forEach(function(pair) {
pair = pair.split('=');
var name = decodeURI(pair[0])
var value = decodeURI(pair[1])
if( name.length )
if (result[name] !== undefined) {
if (!result[name].push) {
result[name] = [result[name]];
}
result[name].push(value || '');
} else {
result[name] = value || '';
}
});
return( result )
}
something
$("a").serializeURLParams();
console yields => {x: Array[3], y: "blah"} where x is an array as is proper JSON
Now, the first will accept the parameters only but the jQuery plugin will take the whole url and return the serialized parameters.
Here's one I use:
var params = {};
window.location.search.substring(1).split('&').forEach(function(pair) {
pair = pair.split('=');
if (pair[1] !== undefined) {
var key = decodeURIComponent(pair[0]),
val = decodeURIComponent(pair[1]),
val = val ? val.replace(/\++/g,' ').trim() : '';
if (key.length === 0) {
return;
}
if (params[key] === undefined) {
params[key] = val;
}
else {
if ("function" !== typeof params[key].push) {
params[key] = [params[key]];
}
params[key].push(val);
}
}
});
console.log(params);
Basic usage, eg.
?a=aa&b=bb
Object {a: "aa", b: "bb"}
Duplicate params, eg.
?a=aa&b=bb&c=cc&c=potato
Object {a: "aa", b: "bb", c: ["cc","potato"]}
Missing keys, eg.
?a=aa&b=bb&=cc
Object {a: "aa", b: "bb"}
Missing values, eg.
?a=aa&b=bb&c
Object {a: "aa", b: "bb"}
The above JSON/regex solutions throw a syntax error on this wacky url:
?a=aa&b=bb&c=&=dd&e
Object {a: "aa", b: "bb", c: ""}
Here's my quick and dirty version, basically its splitting up the URL parameters separated by '&' into array elements, and then iterates over that array adding key/value pairs separated by '=' into an object. I'm using decodeURIComponent() to translate the encoded characters to their normal string equivalents (so %20 becomes a space, %26 becomes '&', etc):
function deparam(paramStr) {
let paramArr = paramStr.split('&');
let paramObj = {};
paramArr.forEach(e=>{
let param = e.split('=');
paramObj[param[0]] = decodeURIComponent(param[1]);
});
return paramObj;
}
example:
deparam('abc=foo&def=%5Basf%5D&xyz=5')
returns
{
abc: "foo"
def:"[asf]"
xyz :"5"
}
The only issue is that xyz is a string and not a number (due to using decodeURIComponent()), but beyond that its not a bad starting point.
//under ES6
const getUrlParamAsObject = (url = window.location.href) => {
let searchParams = url.split('?')[1];
const result = {};
//in case the queryString is empty
if (searchParams!==undefined) {
const paramParts = searchParams.split('&');
for(let part of paramParts) {
let paramValuePair = part.split('=');
//exclude the case when the param has no value
if(paramValuePair.length===2) {
result[paramValuePair[0]] = decodeURIComponent(paramValuePair[1]);
}
}
}
return result;
}
If you need recursion, you can use the tiny js-extension-ling library.
npm i js-extension-ling
const jsx = require("js-extension-ling");
console.log(jsx.queryStringToObject("a=1"));
console.log(jsx.queryStringToObject("a=1&a=3"));
console.log(jsx.queryStringToObject("a[]=1"));
console.log(jsx.queryStringToObject("a[]=1&a[]=pomme"));
console.log(jsx.queryStringToObject("a[0]=one&a[1]=five"));
console.log(jsx.queryStringToObject("http://blabla?foo=bar&number=1234"));
console.log(jsx.queryStringToObject("a[fruits][red][]=strawberry"));
console.log(jsx.queryStringToObject("a[fruits][red][]=strawberry&a[1]=five&a[fruits][red][]=cherry&a[fruits][yellow][]=lemon&a[fruits][yellow][688]=banana"));
This will output something like this:
{ a: '1' }
{ a: '3' }
{ a: { '0': '1' } }
{ a: { '0': '1', '1': 'pomme' } }
{ a: { '0': 'one', '1': 'five' } }
{ foo: 'bar', number: '1234' }
{
a: { fruits: { red: { '0': 'strawberry' } } }
}
{
a: {
'1': 'five',
fruits: {
red: { '0': 'strawberry', '1': 'cherry' },
yellow: { '0': 'lemon', '688': 'banana' }
}
}
}
Note: it's based on locutus parse_str function (https://locutus.io/php/strings/parse_str/).
FIRST U NEED TO DEFINE WHAT'S A GET VAR:
function getVar()
{
this.length = 0;
this.keys = [];
this.push = function(key, value)
{
if(key=="") key = this.length++;
this[key] = value;
this.keys.push(key);
return this[key];
}
}
Than just read:
function urlElement()
{
var thisPrototype = window.location;
for(var prototypeI in thisPrototype) this[prototypeI] = thisPrototype[prototypeI];
this.Variables = new getVar();
if(!this.search) return this;
var variables = this.search.replace(/\?/g,'').split('&');
for(var varI=0; varI<variables.length; varI++)
{
var nameval = variables[varI].split('=');
var name = nameval[0].replace(/\]/g,'').split('[');
var pVariable = this.Variables;
for(var nameI=0;nameI<name.length;nameI++)
{
if(name.length-1==nameI) pVariable.push(name[nameI],nameval[1]);
else var pVariable = (typeof pVariable[name[nameI]] != 'object')? pVariable.push(name[nameI],new getVar()) : pVariable[name[nameI]];
}
}
}
and use like:
var mlocation = new urlElement();
mlocation = mlocation.Variables;
for(var key=0;key<mlocation.keys.length;key++)
{
console.log(key);
console.log(mlocation[mlocation.keys[key]];
}
I needed to also deal with + in the query part of the URL (decodeURIComponent doesn't), so I adapted Wolfgang's code to become:
var search = location.search.substring(1);
search = search?JSON.parse('{"' + search.replace(/\+/g, ' ').replace(/&/g, '","').replace(/=/g,'":"') + '"}',
function(key, value) { return key===""?value:decodeURIComponent(value)}):{};
In my case, I'm using jQuery to get URL-ready form parameters, then this trick to build an object out of it and I can then easily update parameters on the object and rebuild the query URL, e.g.:
var objForm = JSON.parse('{"' + $myForm.serialize().replace(/\+/g, ' ').replace(/&/g, '","').replace(/=/g,'":"') + '"}',
function(key, value) { return key===""?value:decodeURIComponent(value)});
objForm.anyParam += stringToAddToTheParam;
var serializedForm = $.param(objForm);

Access JavaScript property case-insensitively?

Assume I have an object:
var obj = {
foo:"bar",
fizz:"buzz"
};
I need to access a property of that object dynamically like so:
var objSetter = function(prop,val){
obj[prop] = val;
}
No problems there, except for that prop needs to be case insensitive in case the property name is passed into the function as, say, Foo instead of foo.
So how can I point to an object's property by name without regard to case? I would like to avoid iterating the entire object if possible.
Try this:
var myObject = { "mIxeDCaSEKeY": "value" };
var searchKey = 'mixedCaseKey';
var asLowercase = searchKey.toLowerCase();
myObject[Object.keys(myObject).find(key => key.toLowerCase() === asLowercase)];
You can alternatively already provide the searchKey in lowercase.
If you want it as a function:
/**
* #param {Object} object
* #param {string} key
* #return {any} value
*/
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowerCase();
return object[Object.keys(object)
.find(k => k.toLowerCase() === asLowercase)
];
}
If the key can't be found, then it'll return undefined, just like normal.
If you need to support older browsers, then you can use filter instead:
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowercase();
return object[Object.keys(object).filter(function(k) {
return k.toLowerCase() === asLowercase;
})[0]];
}
I suggest using the polyfills for Object.keys() and Array.filter() if you need even older support.
Note: If you want to also check non-enumerable keys, use Object.getOwnPropertyNames() instead of Object.keys().
Nerdy Note: This assumes your Object doesn't have a key undefined (eg: const foo = {[undefined]: 'bar'};). That's just weird.
Compare all the properties of obj with prop.
var objSetter = function(prop,val){
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
obj[p] = val;
break;
}
}
}
For this, I prefer using the prototype over a standalone function just for ease of use and expressiveness. I just don't like funneling objects into functions if I don't have to.
Also, while the accepted answer works, I wanted a more comprehensive solution for both getting and setting that would behave as much like the native dot notation or bracket notation as possible.
With that in mind, I created a couple prototype functions for setting/getting an object property without regard to case. You have to remember to be VERY responsible when adding to the Object prototype. Especially when using JQuery and other libraries. Object.defineProperty() with enumerable set to false was used specifically to avoid conflict with JQuery. I also didn't bother naming the functions anything that indicates they are case-insensitive, but you certainly could. I like shorter names.
Here's the getter:
Object.defineProperty(Object.prototype, "getProp", {
value: function (prop) {
var key,self = this;
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
return self[key];
}
}
},
//this keeps jquery happy
enumerable: false
});
Here's the setter:
Object.defineProperty(Object.prototype, "setProp", {
value: function (prop, val) {
var key,self = this;
var found = false;
if (Object.keys(self).length > 0) {
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
//set existing property
found = true;
self[key] = val;
break;
}
}
}
if (!found) {
//if the property was not found, create it
self[prop] = val;
}
return val;
},
//this keeps jquery happy
enumerable: false
});
Now that we've created those functions, our code is super clean and concise and just works.
Case-insensitive getting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.getProp("FOO"); //returns 'bar'
obj.getProp("fOO"); //returns 'bar'
obj.getProp("CAMELCASE"); //returns 'humpy'
obj.getProp("CamelCase"); //returns 'humpy'
Case-insensitive setting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.setProp('CAmelCasE', 'super humpy'); //sets prop 'camelCase' to 'super humpy'
obj.setProp('newProp', 'newval'); //creates prop 'newProp' and sets val to 'newval'
obj.setProp('NewProp', 'anotherval'); //sets prop 'newProp' to 'anotherval'
Yet another variation on those already presented which pushes the iteration down into the Underscore/Lodash findKey function:
var _ = require('underscore');
var getProp = function (obj, name) {
var realName = _.findKey(obj, function (value, key) {
return key.toLowerCase() === name.toLowerCase();
});
return obj[realName];
};
For example:
var obj = { aa: 1, bB: 2, Cc: 3, DD: 4 };
getProp(obj, 'aa'); // 1
getProp(obj, 'AA'); // 1
getProp(obj, 'bb'); // 2
getProp(obj, 'BB'); // 2
getProp(obj, 'cc'); // 3
getProp(obj, 'CC'); // 3
getProp(obj, 'dd'); // 4
getProp(obj, 'DD'); // 4
getProp(obj, 'EE'); // undefined
This answer requires ES6.
const x = { 'aB': 1, 'X-Total-Count': 10, y3: 2 }
console.log(x[Object.keys(x).find(key=>{return key.match(/^ab$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^x-total-count$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^y3$/i)})])
It seems to me like a good candidate for Proxy with traps to convert string keys to either upper case or lower case and behaving like a regular object.
This works with either notation: dots or braquets
Here is the code:
'use strict';
function noCasePropObj(obj)
{
var handler =
{
get: function(target, key)
{
//console.log("key: " + key.toString());
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
return target[key];
return target[uKey];
}
return target[key];
},
set: function(target, key, value)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
target[key] = value;
target[uKey] = value;
}
else
target[key] = value;
},
deleteProperty: function(target, key)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
delete target[key];
if (uKey in target)
delete target[uKey];
}
else
delete target[key];
},
};
function checkAtomic(value)
{
if (typeof value == "object")
return new noCasePropObj(value); // recursive call only for Objects
return value;
}
var newObj;
if (typeof obj == "object")
{
newObj = new Proxy({}, handler);
// traverse the Original object converting string keys to upper case
for (var key in obj)
{
if (typeof key == "string")
{
var objKey = key.toUpperCase();
if (!(key in newObj))
newObj[objKey] = checkAtomic(obj[key]);
}
}
}
else if (Array.isArray(obj))
{
// in an array of objects convert to upper case string keys within each row
newObj = new Array();
for (var i = 0; i < obj.length; i++)
newObj[i] = checkAtomic(obj[i]);
}
return newObj; // object with upper cased keys
}
// Use Sample:
var b = {Name: "Enrique", last: "Alamo", AdDrEsS: {Street: "1233 Main Street", CITY: "Somewhere", zip: 33333}};
console.log("Original: " + JSON.stringify(b)); // Original: {"Name":"Enrique","last":"Alamo","AdDrEsS":{"Street":"1233 Main Street","CITY":"Somewhere","zip":33333}}
var t = noCasePropObj(b);
console.log(JSON.stringify(t)); // {"NAME":"Enrique","LAST":"Alamo","ADDRESS":{"STREET":"1233 Main Street","CITY":"Somewhere","ZIP":33333}}
console.log('.NaMe:' + t.NaMe); // .NaMe:Enrique
console.log('["naME"]:' + t["naME"]); // ["naME"]:Enrique
console.log('.ADDreSS["CitY"]:' + t.ADDreSS["CitY"]); // .ADDreSS["CitY"]:Somewhere
console.log('check:' + JSON.stringify(Object.getOwnPropertyNames(t))); // check:["NAME","LAST","ADDRESS"]
console.log('check2:' + JSON.stringify(Object.getOwnPropertyNames(t['AddresS']))); // check2:["STREET","CITY","ZIP"]
You could do this in order to "normalize" prop
var normalizedProp = prop.toLowerCase();
obj[normalizedProp] = val;
const getPropertyNoCase = (obj, prop) => obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
or
const getPropertyNoCase = (obj, prop) => {
const lowerProp = prop.toLowerCase(obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
}
The ES6 example posted by #nilloc is incorrect and will break in use.
Here is a working example:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i)
} else {
return result;
}
},null)]);
or better yet, it should return undefined if the key doesn't exist:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i) || undefined
} else {
return result;
}
},undefined)]);
One consideration is that the above example will return the last matching key in the object if there are multiple keys that match.
Here is an example with the code made into a function:
/**
* #param {Object} object
* #param {string} key
* #return {string||undefined} value || undefined
*/
function getKeyCase(obj,key) {
const re = new RegExp(key,"i");
return Object.keys(obj).reduce((result,key)=>{
if (!result) {
return key.match(re) || undefined
} else {
return result;
}
},undefined);
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[getKeyCase(x,"x-total-count")]);
Its really sad that the iteration can't be skipped as it seems. For me what is acceptable but may not be for everyone is to shape the object one time via iteration and then use it in regular hashmap fashion.
const hashmap = {
'FOO': 'foo as in function programming',
'bar': 'bar is in baz',
};
const shapedmap = Object.entries(hashmap).reduce(
(acc, [key, val]) => (acc[key.toUpperCase()] = val, acc), {}
);
for (const term of ['foo', 'bar', 'baz']) {
const match = shapedmap[term.toUpperCase()]
match && console.log('awesome, we got the term.', match);
};
Even if it just one time lookup has to be performed, it shouldn't less performant as any other iteration solution since after 1 pass, the lookup speed is constant. (I guess).
This is an old question, but it was the first one I found.
As #ZachSmith says, you can use a Proxy.
Here's some example code:
function lowercase(oldKey) {
// Check that it's a string.
return typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey;
}
const propertiesMap = new Map(
Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]])
);
const caseInsensitiveGetHandler = {
get: function(target, property, receiver) {
return propertiesMap.get(lowercase(property));
}
};
obj = new Proxy(obj, caseInsensitiveGetHandler);
For my use case, I only needed to proxy the object's getter, but you may need to implement more of the Proxy methods.
There is no need for any iteration. Since prop might not be a string, it should be coerced to a string first where appropriate since that's what objects do natively. A simple getter function is:
function objGetter(prop) {
return obj[String(prop).toLowerCase()];
}
If there is a requirement is to restring access to own properties:
function objGetter(prop) {
prop = String(prop).toLowerCase();
if (obj.hasOwnProperty(prop)) {
return obj.prop;
}
}
and a setter:
function objSetter(prop, val) {
obj[String(prop).toLowerCase()] = val;
}
Heres a very simple code to do this
Assuming that data is the array of objects like
data=[{"A":"bc","B":"nn"}]
var data=data.reduce(function(prev, curr) {
var cc = curr; // current value
var K = Object.keys(cc); // get all keys
var n = {};
for (var i = 0; i < K.length; i++) {
var key = K[i];//get hte key
n[key.toLowerCase()] = cc[key] // convert to lowercase and assign
}
prev.push(n) // push to array
return prev;
}, [])
Output will be
data=[{"a":"bc","b":"nn"}]
You might only need to do case-insensitive matching (usually expensive because of object iteration) IF a case-sensitive match (cheap and quick) fails.
Say you have:
var your_object = { "Chicago" : 'hi' , "deTroiT" : 'word' , "atlanta" : 'get r dun' } ;
And you have, for whatever reason, the_value, Detroit:
if( your_object.hasOwnProperty( the_value ) )
{
// do what you need to do here
}
else
{ // since the case-sensitive match did not succeed,
// ... Now try a the more-expensive case-insensitive matching
for( let lvs_prop in your_object )
{ if( the_value.toLowerCase() == lvs_prop.toLowerCase() )
{
// do what you need to do here
break ;
} ;
}
} ;
why would we do it that complicated when we simply can make it all lower case:
var your_object = {
"chickago" : 'hi' ,
"detroit" : 'word',
"atlanta" : 'get r dun',
GetName: function (status) {
return this[status].name;
} };
to call it: your_object.GetName(your_var.toLowerCase());
Another simple way:
function getVal(obj, prop){
var val;
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
val = obj[p]
break;
}
}
return val;
}
Use it like this:
var obj = {
foo:"bar",
fizz:"buzz"
};
getVal(obj,"FoO") -> returns "bar"
Here is a nice recursive function that allows you to traverse a javascript object in a case-insensitive way:
let testObject = {'a': {'B': {'cC': [1,2,3]}}}
let testSeq = ['a','b','cc']
function keySequence(o, kseq) {
if(kseq.length==0){ return o; }
let validKeys = Object.keys(o).filter(k=>k.toLowerCase()==kseq[0].toLowerCase());
if(validKeys.length==0) { return `Incorrect Key: ${kseq[0]}` }
return keySequence(o[validKeys[0]], kseq.slice(1))
}
keySequence(testObject, testSeq); //returns [1,2,3]
This will convert everything to lowercase, but in a bind this could help if you are not concerned with retaining case.
var somedata = {
"MixEdCase": 1234
}
var temp = JSON.parse(JSON.stringify(somedata).toLowerCase());
console.log(temp.mixedcase);
// or
console.log(temp["mixedcase"]);
So, you will need to get the object key that matches the case of the existing object, then use this to do your object update.
const obj = {
foo:"bar",
fizz:"buzz"
};
// to get obj.foo or obj.FOO or obj.foO returning "bar"
// create regex expression of case insensitive version of the key string
const regex=passedKey=> new RegExp(`^${passedKey}$`,'gi');
// find the key that matches the string you are passing
const formattedKey=passedKey=>Object.keys(obj).find(key=>regex(passedKey).test(key));
formattedKey('Foo'); // returns foo
formattedKey('FoO'); // returns foo
// consequently you can can use it like wise
obj[formattedKey('Foo')] // returns bar
obj[formattedKey('FoO')] // returns bar
obj[formattedKey('foo')] // returns bar

String to object in JS

I have a string as
string = "firstName:name1, lastName:last1";
now I need one object obj such that
obj = {firstName:name1, lastName:last1}
How can I do this in JS?
Actually, the best solution is using JSON:
Documentation
JSON.parse(text[, reviver]);
Examples:
1)
var myobj = JSON.parse('{ "hello":"world" }');
alert(myobj.hello); // 'world'
2)
var myobj = JSON.parse(JSON.stringify({
hello: "world"
});
alert(myobj.hello); // 'world'
3)
Passing a function to JSON
var obj = {
hello: "World",
sayHello: (function() {
console.log("I say Hello!");
}).toString()
};
var myobj = JSON.parse(JSON.stringify(obj));
myobj.sayHello = new Function("return ("+myobj.sayHello+")")();
myobj.sayHello();
Your string looks like a JSON string without the curly braces.
This should work then:
obj = eval('({' + str + '})');
WARNING: this introduces significant security holes such as XSS with untrusted data (data that is entered by the users of your application.)
If I'm understanding correctly:
var properties = string.split(', ');
var obj = {};
properties.forEach(function(property) {
var tup = property.split(':');
obj[tup[0]] = tup[1];
});
I'm assuming that the property name is to the left of the colon and the string value that it takes on is to the right.
Note that Array.forEach is JavaScript 1.6 -- you may want to use a toolkit for maximum compatibility.
This simple way...
var string = "{firstName:'name1', lastName:'last1'}";
eval('var obj='+string);
alert(obj.firstName);
output
name1
Since JSON.parse() method requires the Object keys to be enclosed within quotes for it to work correctly, we would first have to convert the string into a JSON formatted string before calling JSON.parse() method.
var obj = '{ firstName:"John", lastName:"Doe" }';
var jsonStr = obj.replace(/(\w+:)|(\w+ :)/g, function(matchedStr) {
return '"' + matchedStr.substring(0, matchedStr.length - 1) + '":';
});
obj = JSON.parse(jsonStr); //converts to a regular object
console.log(obj.firstName); // expected output: John
console.log(obj.lastName); // expected output: Doe
This would work even if the string has a complex object (like the following) and it would still convert correctly. Just make sure that the string itself is enclosed within single quotes.
var strObj = '{ name:"John Doe", age:33, favorites:{ sports:["hoops", "baseball"], movies:["star wars", "taxi driver"] }}';
var jsonStr = strObj.replace(/(\w+:)|(\w+ :)/g, function(s) {
return '"' + s.substring(0, s.length-1) + '":';
});
var obj = JSON.parse(jsonStr);
console.log(obj.favorites.movies[0]); // expected output: star wars
If you have a string like foo: 1, bar: 2 you can convert it to a valid obj with:
str
.split(',')
.map(x => x.split(':').map(y => y.trim()))
.reduce((a, x) => {
a[x[0]] = x[1];
return a;
}, {});
Thanks to niggler in #javascript for that.
Update with explanations:
const obj = 'foo: 1, bar: 2'
.split(',') // split into ['foo: 1', 'bar: 2']
.map(keyVal => { // go over each keyVal value in that array
return keyVal
.split(':') // split into ['foo', '1'] and on the next loop ['bar', '2']
.map(_ => _.trim()) // loop over each value in each array and make sure it doesn't have trailing whitespace, the _ is irrelavent because i'm too lazy to think of a good var name for this
})
.reduce((accumulator, currentValue) => { // reduce() takes a func and a beginning object, we're making a fresh object
accumulator[currentValue[0]] = currentValue[1]
// accumulator starts at the beginning obj, in our case {}, and "accumulates" values to it
// since reduce() works like map() in the sense it iterates over an array, and it can be chained upon things like map(),
// first time through it would say "okay accumulator, accumulate currentValue[0] (which is 'foo') = currentValue[1] (which is '1')
// so first time reduce runs, it starts with empty object {} and assigns {foo: '1'} to it
// second time through, it "accumulates" {bar: '2'} to it. so now we have {foo: '1', bar: '2'}
return accumulator
}, {}) // when there are no more things in the array to iterate over, it returns the accumulated stuff
console.log(obj)
Confusing MDN docs:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
Demo: http://jsbin.com/hiduhijevu/edit?js,console
Function:
const str2obj = str => {
return str
.split(',')
.map(keyVal => {
return keyVal
.split(':')
.map(_ => _.trim())
})
.reduce((accumulator, currentValue) => {
accumulator[currentValue[0]] = currentValue[1]
return accumulator
}, {})
}
console.log(str2obj('foo: 1, bar: 2')) // see? works!
You need use JSON.parse() for convert String into a Object:
var obj = JSON.parse('{ "firstName":"name1", "lastName": "last1" }');
if you're using JQuery:
var obj = jQuery.parseJSON('{"path":"/img/filename.jpg"}');
console.log(obj.path); // will print /img/filename.jpg
REMEMBER: eval is evil! :D
I implemented a solution in a few lines of code which works quite reliably.
Having an HTML element like this where I want to pass custom options:
<div class="my-element"
data-options="background-color: #dadada; custom-key: custom-value;">
</div>
a function parses the custom options and return an object to use that somewhere:
function readCustomOptions($elem){
var i, len, option, options, optionsObject = {};
options = $elem.data('options');
options = (options || '').replace(/\s/g,'').split(';');
for (i = 0, len = options.length - 1; i < len; i++){
option = options[i].split(':');
optionsObject[option[0]] = option[1];
}
return optionsObject;
}
console.log(readCustomOptions($('.my-element')));
In your case, The short and beautiful code
Object.fromEntries(str.split(',').map(i => i.split(':')));
I'm using JSON5, and it's works pretty well.
The good part is it contains no eval and no new Function, very safe to use.
string = "firstName:name1, lastName:last1";
This will work:
var fields = string.split(', '),
fieldObject = {};
if( typeof fields === 'object') ){
fields.each(function(field) {
var c = property.split(':');
fieldObject[c[0]] = c[1];
});
}
However it's not efficient. What happens when you have something like this:
string = "firstName:name1, lastName:last1, profileUrl:http://localhost/site/profile/1";
split() will split 'http'. So i suggest you use a special delimiter like pipe
string = "firstName|name1, lastName|last1";
var fields = string.split(', '),
fieldObject = {};
if( typeof fields === 'object') ){
fields.each(function(field) {
var c = property.split('|');
fieldObject[c[0]] = c[1];
});
}
const text = '{"name":"John", "age":30, "city":"New York"}';
const myArr = JSON.parse(text);
document.getElementById("demo").innerHTML = myArr.name;
This is universal code , no matter how your input is long but in same schema if there is : separator :)
var string = "firstName:name1, lastName:last1";
var pass = string.replace(',',':');
var arr = pass.split(':');
var empty = {};
arr.forEach(function(el,i){
var b = i + 1, c = b/2, e = c.toString();
if(e.indexOf('.') != -1 ) {
empty[el] = arr[i+1];
}
});
console.log(empty)
Here is my approach to handle some edge cases like having whitespaces and other primitive types as values
const str = " c:234 , d:sdfg ,e: true, f:null, g: undefined, h:name ";
const strToObj = str
.trim()
.split(",")
.reduce((acc, item) => {
const [key, val = ""] = item.trim().split(":");
let newVal = val.trim();
if (newVal == "null") {
newVal = null;
} else if (newVal == "undefined") {
newVal = void 0;
} else if (!Number.isNaN(Number(newVal))) {
newVal = Number(newVal);
}else if (newVal == "true" || newVal == "false") {
newVal = Boolean(newVal);
}
return { ...acc, [key.trim()]: newVal };
}, {});
In your case
var KeyVal = string.split(", ");
var obj = {};
var i;
for (i in KeyVal) {
KeyVal[i] = KeyVal[i].split(":");
obj[eval(KeyVal[i][0])] = eval(KeyVal[i][1]);
}
var stringExample = "firstName:name1, lastName:last1 | firstName:name2, lastName:last2";
var initial_arr_objects = stringExample.split("|");
var objects =[];
initial_arr_objects.map((e) => {
var string = e;
var fields = string.split(','),fieldObject = {};
if( typeof fields === 'object') {
fields.forEach(function(field) {
var c = field.split(':');
fieldObject[c[0]] = c[1]; //use parseInt if integer wanted
});
}
console.log(fieldObject)
objects.push(fieldObject);
});
"objects" array will have all the objects
I know this is an old post but didn't see the correct answer for the question.
var jsonStrig = '{';
var items = string.split(',');
for (var i = 0; i < items.length; i++) {
var current = items[i].split(':');
jsonStrig += '"' + current[0] + '":"' + current[1] + '",';
}
jsonStrig = jsonStrig.substr(0, jsonStrig.length - 1);
jsonStrig += '}';
var obj = JSON.parse(jsonStrig);
console.log(obj.firstName, obj.lastName);
Now you can use obj.firstName and obj.lastName to get the values as you could do normally with an object.
You don't have to always convert to JSON
So here "person begin as a string!" Finally, "person is converted to object", no necessarily to JSON.
function strToObj(e){if(typeof e=="string"){ let obj=new Function("return" +e); try{return obj()}catch{console.log("Fix, String no allowed to object")}}else{console.log("it is not a string") } };
//Example, person is a string
let person='{firstName:"John", lastName:"Doe", id: 55, fullName:function(){return this.firstName+" "+this.lastName} }';
console.log(strToObj(person));
And it run functions internal to the object without major issues if it is called:
person=strToObj(person); console.log(person.fullName())
Simply, string = "firstName:name1, lastName:last1";
let string = "firstName:name1, lastName:last1";
let object= strToObj("{"+string+"}");
console.log(object)

Categories

Resources