JSON.stringify whitelisting with nested objects - javascript

Given following example:
var test = {
"company_name": "Foobar",
"example": "HelloWorld",
"address": {
"street": "My Street 12",
"example": "BarFoo",
"details": "Berlin",
}
}
console.log(JSON.stringify(test, ['company_name','address','street','example']));
// What I actually want
// console.log(JSON.stringify(test, ['company_name','address.street','address.example']));
How can I use JSON's stringify function to deal with nested objects properly?
Since I have huge JSON objects it happens that a key of a nested object is identical to it's "parent" object. I would like to specify my whitelist more granulary.

If you're willing to go to the effort of whitelisting, then you can establish an array of valid keys, which can provide the ability to nest similar to how many systems do JSON nesting (a . separator, or any separator of your choosing).
var whitelistedObj = whitelistJson(obj, ["company_name", "example", "address.street", "address.example"]);
function whitelistJson(obj, whitelist, separator) {
var object = {};
for (var i = 0, length = whitelist.length; i < length; ++i) {
var k = 0,
names = whitelist[i].split(separator || '.'),
value = obj,
name,
count = names.length - 1,
ref = object,
exists = true;
// fill in any empty objects from first name to end without
// picking up neighboring fields
while (k < count) { // walks to n - 1
name = names[k++];
value = value[name];
if (typeof value !== 'undefined') {
if (typeof object[name] === 'undefined') {
ref[name] = {};
}
ref = ref[name];
}
else {
exists = false;
break;
}
}
if (exists) {
ref[names[count]] = value[names[count]];
}
}
return object;
}
I have a JSFiddle showing its usage as well (to ensure it actually worked on my admittedly small sample set).

You can add toJSON method in your huge JSON objects:
var test = {
"company_name": "Foobar",
"example": "HelloWorld",
"address": {
"street": "My Street 12",
"example": "BarFoo",
"details": "Berlin",
},
toJSON: function () {
return {
company_name: this.company_name,
address: {
street: this.address.street,
example: this.address.example
}
}
}
}
And, you get:
console.log(JSON.stringify(test)); // "{"company_name":"Foobar","address":{"street":"My Street 12","example":"BarFoo"}}"
Or, you can use some filter function: (this function using lodash)
function filter(object, keys, sep) {
sep = sep || '.';
var result = {};
_.each(keys, function (key) {
var keyParts = key.split(sep),
res = object,
branch = {},
branchPart = branch;
for (var i = 0; i < keyParts.length; i++) {
key = keyParts[i];
if (!_.has(res, key)) {
return;
}
branchPart[key] = _.isObject(res[key]) ? {} : res[key];
branchPart = branchPart[key];
res = res[key];
}
_.merge(result, branch);
});
return result;
}
console.log(JSON.stringify(filter(test, ['company_name', 'address.street', 'address.example']))); // "{"company_name":"Foobar","address":{"street":"My Street 12","example":"BarFoo"}}"
Check out jsfiddle http://jsfiddle.net/SaKhG/

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)

How to normalize JS Object with special char property

Hey guys I'm working on a project and I'm getting an object that looks like this:
{
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york",
}
How do I normalize that? So I can access the properties that I need?
Thanks
Here is a es5 approach to normalize the object
function normalisedObject(object) {
var normalised = {};
for (var key in object) {
//regex to capture all [.*] groups
var matches = key.match(/\[(.*?)\]/g);
if (matches) {
var temp = normalised;
while (matches.length > 0) {
//get first key and replace []
var newKey = matches[0].replace(/\[|\]/g, "");
if (matches.length !== 1) {
//keep traverse if the left over keys are greater than 1
temp = temp[newKey] || (temp[newKey] = {}); //assign empty object
} else {
temp[newKey] = object[key];
}
//poll
matches.shift();
}
}
}
return normalised;
}
//example
const t1 = performance.now();
var object = {
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york"
};
var normalised = normalisedObject(object);
const t2 = performance.now();
console.log(`Operation took ${t2 - t1}ms`);
console.log(normalised);
When an object's keys don't allow you to use simple dot notation, ie obj.property, you can use square-bracket notation instead, eg
const lang = obj["userChoice[language]"]
But I have a feeling you'd actually like to transform that object into something resembling this
{
userChoice: {
language: "en",
responses: {
favColor: "black",
favCity: "new york"
}
}
}
If that's the case, you need to reduce the object entries (key / value pairs) to a new object, parsing out the key paths and building new, inner objects as you go
const obj = {
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york",
}
const t1 = performance.now()
const normalised = Object.entries(obj).reduce((c, [ key, val ]) => {
// get the key parts as a path array
const path = key.replace(/]/g, "").split(/\[/)
// pop the last property for assigning the value later
const prop = path.pop()
// determine the inner-most object by path
const inner = path.reduce((o, k) => {
// create a new object if it doesn't exist
return o[k] ?? (o[k] = {})
}, c)
// assign the value
inner[prop] = val
return c
}, {})
const t2 = performance.now()
console.info(normalised)
console.log(`Operation took ${t2 - t1}ms`)
.as-console-wrapper { max-height: 100% !important; }
Note that if you start throwing any array properties in, eg userChoice[foo][0], this won't work.

Need guidance to amend excel to JSON javascript code to output one JSON per set of data

I'm not from a programming background but I have a requirement in that I need to be able to convert excel data to JSON format.
I have found the below package which is exactly what I need as I can control the object types from the header names rather than hard code for each field. It also allows me to column orientate my data i.e. the header in column A.
The package can be found here --> excel-as-json
The output looks like this:
[
{
"firstName": "Jihad",
"lastName": "",
"address": {
"street": "12 Beaver Court",
"city": "",
"state": "CO",
"zip": "81615"
},
"isEmployee": true,
"phones": [
{
"type": "home",
"number": "123.456.7890"
},
{
"type": "work",
"number": "098.765.4321"
}
],
"aliases": [
"stormagedden",
"bob"
]
},
{
"firstName": "Marcus",
"lastName": "Rivapoli",
"address": {
"street": "16 Vail Rd",
"city": "Vail",
"state": "CO",
"zip": "75850"
},
"isEmployee": false,
"phones": [
{
"type": "home",
"number": "123.456.7891"
},
{
"type": "work",
"number": "098.765.4322"
}
],
"aliases": [
"mac",
"markie"
]
}
]
What I need to do and what I'm struggling with is two fold:
I need to remove the square brackets from the beginning and end of the generated JSON
I need to be able to generate multiple JSONs i.e. a JSON for each column of data. So if I have data in column B and C, I need it to generate 2 JSON files with different names (ideally with the name of a specific attribute value)
The code is:
// Generated by CoffeeScript 2.2.4
(function() {
// Create a list of json objects; 1 object per excel sheet row
// Assume: Excel spreadsheet is a rectangle of data, where the first row is
// object keys and remaining rows are object values and the desired json
// is a list of objects. Alternatively, data may be column oriented with
// col 0 containing key names.
// Dotted notation: Key row (0) containing firstName, lastName, address.street,
// address.city, address.state, address.zip would produce, per row, a doc with
// first and last names and an embedded doc named address, with the address.
// Arrays: may be indexed (phones[0].number) or flat (aliases[]). Indexed
// arrays imply a list of objects. Flat arrays imply a semicolon delimited list.
// USE:
// From a shell
// coffee src/excel-as-json.coffee
var BOOLTEXT, BOOLVALS, _DEFAULT_OPTIONS, _validateOptions, assign, convert, convertValue, convertValueList, excel, fs, isArray, parseKeyName, path, processFile, transpose, write,
indexOf = [].indexOf;
fs = require('fs');
path = require('path');
excel = require('excel');
BOOLTEXT = ['true', 'false'];
BOOLVALS = {
'true': true,
'false': false
};
isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
// Extract key name and array index from names[1] or names[]
// return [keyIsList, keyName, index]
// for names[1] return [true, keyName, index]
// for names[] return [true, keyName, undefined]
// for names return [false, keyName, undefined]
parseKeyName = function(key) {
var index;
index = key.match(/\[(\d+)\]$/);
switch (false) {
case !index:
return [true, key.split('[')[0], Number(index[1])];
case key.slice(-2) !== '[]':
return [true, key.slice(0, -2), void 0];
default:
return [false, key, void 0];
}
};
// Convert a list of values to a list of more native forms
convertValueList = function(list, options) {
var item, j, len, results;
results = [];
for (j = 0, len = list.length; j < len; j++) {
item = list[j];
results.push(convertValue(item, options));
}
return results;
};
// Convert values to native types
// Note: all values from the excel module are text
convertValue = function(value, options) {
var testVal;
// isFinite returns true for empty or blank strings, check for those first
if (value.length === 0 || !/\S/.test(value)) {
return value;
} else if (isFinite(value)) {
if (options.convertTextToNumber) {
return Number(value);
} else {
return value;
}
} else {
testVal = value.toLowerCase();
if (indexOf.call(BOOLTEXT, testVal) >= 0) {
return BOOLVALS[testVal];
} else {
return value;
}
}
};
// Assign a value to a dotted property key - set values on sub-objects
assign = function(obj, key, value, options) {
var i, index, j, keyIsList, keyName, ref, ref1;
if (typeof key !== 'object') {
// On first call, a key is a string. Recursed calls, a key is an array
key = key.split('.');
}
// Array element accessors look like phones[0].type or aliases[]
[keyIsList, keyName, index] = parseKeyName(key.shift());
if (key.length) {
if (keyIsList) {
// if our object is already an array, ensure an object exists for this index
if (isArray(obj[keyName])) {
if (!obj[keyName][index]) {
for (i = j = ref = obj[keyName].length, ref1 = index; (ref <= ref1 ? j <= ref1 : j >= ref1); i = ref <= ref1 ? ++j : --j) {
obj[keyName].push({});
}
}
} else {
// else set this value to an array large enough to contain this index
obj[keyName] = (function() {
var k, ref2, results;
results = [];
for (i = k = 0, ref2 = index; (0 <= ref2 ? k <= ref2 : k >= ref2); i = 0 <= ref2 ? ++k : --k) {
results.push({});
}
return results;
})();
}
return assign(obj[keyName][index], key, value, options);
} else {
if (obj[keyName] == null) {
obj[keyName] = {};
}
return assign(obj[keyName], key, value, options);
}
} else {
if (keyIsList && (index != null)) {
console.error(`WARNING: Unexpected key path terminal containing an indexed list for <${keyName}>`);
console.error("WARNING: Indexed arrays indicate a list of objects and should not be the last element in a key path");
console.error("WARNING: The last element of a key path should be a key name or flat array. E.g. alias, aliases[]");
}
if (keyIsList && (index == null)) {
if (value !== '') {
return obj[keyName] = convertValueList(value.split(';'), options);
} else if (!options.omitEmptyFields) {
return obj[keyName] = [];
}
} else {
if (!(options.omitEmptyFields && value === '')) {
return obj[keyName] = convertValue(value, options);
}
}
}
};
// Transpose a 2D array
transpose = function(matrix) {
var i, j, ref, results, t;
results = [];
for (i = j = 0, ref = matrix[0].length; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
results.push((function() {
var k, len, results1;
results1 = [];
for (k = 0, len = matrix.length; k < len; k++) {
t = matrix[k];
results1.push(t[i]);
}
return results1;
})());
}
return results;
};
// Convert 2D array to nested objects. If row oriented data, row 0 is dotted key names.
// Column oriented data is transposed
convert = function(data, options) {
var index, item, j, k, keys, len, len1, result, row, rows, value;
if (options.isColOriented) {
data = transpose(data);
}
keys = data[0];
rows = data.slice(1);
result = [];
for (j = 0, len = rows.length; j < len; j++) {
row = rows[j];
item = [];
for (index = k = 0, len1 = row.length; k < len1; index = ++k) {
value = row[index];
assign(item, keys[index], value, options);
}
result.push(item);
}
return result;
};
// Write JSON encoded data to file
// call back is callback(err)
write = function(data, dst, callback) {
var dir;
// Create the target directory if it does not exist
dir = path.dirname(dst);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return fs.writeFile(dst, JSON.stringify(data, null, 2), function(err) {
if (err) {
return callback(`Error writing file ${dst}: ${err}`);
} else {
return callback(void 0);
}
});
};
// src: xlsx file that we will read sheet 0 of
// dst: file path to write json to. If null, simply return the result
// options: see below
// callback(err, data): callback for completion notification
// options:
// sheet: string; 1: numeric, 1-based index of target sheet
// isColOriented: boolean: false; are objects stored in excel columns; key names in col A
// omitEmptyFields: boolean: false: do not include keys with empty values in json output. empty values are stored as ''
// TODO: this is probably better named omitKeysWithEmptyValues
// convertTextToNumber boolean: true; if text looks like a number, convert it to a number
// convertExcel(src, dst) <br/>
// will write a row oriented xlsx sheet 1 to `dst` as JSON with no notification
// convertExcel(src, dst, {isColOriented: true}) <br/>
// will write a col oriented xlsx sheet 1 to file with no notification
// convertExcel(src, dst, {isColOriented: true}, callback) <br/>
// will write a col oriented xlsx to file and notify with errors and parsed data
// convertExcel(src, null, null, callback) <br/>
// will parse a row oriented xslx using default options and return errors and the parsed data in the callback
_DEFAULT_OPTIONS = {
sheet: '1',
isColOriented: false,
omitEmptyFields: false,
convertTextToNumber: false
};
// Ensure options sane, provide defaults as appropriate
_validateOptions = function(options) {
if (!options) {
options = _DEFAULT_OPTIONS;
} else {
if (!options.hasOwnProperty('sheet')) {
options.sheet = '1';
} else {
if (!isNaN(parseFloat(options.sheet)) && isFinite(options.sheet)) {
if (options.sheet < 1) {
options.sheet = '1';
} else {
// could be 3 or '3'; force to be '3'
options.sheet = '' + options.sheet;
}
} else {
// something bizarre like true, [Function: isNaN], etc
options.sheet = '1';
}
}
if (!options.hasOwnProperty('isColOriented')) {
options.isColOriented = false;
}
if (!options.hasOwnProperty('omitEmptyFields')) {
options.omitEmptyFields = false;
}
if (!options.hasOwnProperty('convertTextToNumber')) {
options.convertTextToNumber = false;
}
}
return options;
};
processFile = function(src, dst, options = _DEFAULT_OPTIONS, callback = void 0) {
options = _validateOptions(options);
if (!callback) {
callback = function(err, data) {};
}
// NOTE: 'excel' does not properly bubble file not found and prints
// an ugly error we can't trap, so look for this common error first
if (!fs.existsSync(src)) {
return callback(`Cannot find src file ${src}`);
} else {
return excel(src, options.sheet, function(err, data) {
var result;
if (err) {
return callback(`Error reading ${src}: ${err}`);
} else {
result = convert(data, options);
if (dst) {
return write(result, dst, function(err) {
if (err) {
return callback(err);
} else {
return callback(void 0, result);
}
});
} else {
return callback(void 0, result);
}
}
});
}
};
// This is the single expected module entry point
exports.processFile = processFile;
// Unsupported use
// Exposing remaining functionality for unexpected use cases, testing, etc.
exports.assign = assign;
exports.convert = convert;
exports.convertValue = convertValue;
exports.parseKeyName = parseKeyName;
exports._validateOptions = _validateOptions;
exports.transpose = transpose;
}).call(this);
This is untested, but should get you close.
Try adding a function like this into the code (along with the other listed functions, like isArray, parseKeyName, etc.). Then in the processFile function, in the line return write(result, dst, function(err) {, replace write with writeEach.
See the comments in the code for clarification of what it's intended to do.
writeEach = function(data, dst, callback){
// Loops through columns, calling fs.writeFile for each one
data.forEach(function(person){ // argument name determines the name by which we will refer to the current column
var homePhoneDigits = person.phones[0]["number"].split(".").join(""); //removes `.` from phone number
var personDst = homePhoneDigits + ".json"; // uses digits of home phone as destination name
// pattern copied from 'write' function, with variable names changed
var dir;
dir = path.dirname(personDst);
if (!fs.existsSync(dir)) { fs.mkdirSync(dir); }
fs.writeFile(personDst, JSON.stringify(person, null, 2); //writes the current column to a file
});
return callback(void 0); // currently ignores any write errors
};

How to iterate over json array in javascript

Yes there are many post regarding this.But my doubt is little different.I have following array for example
var dictionary = {
"12Jan2013": [{
"id": "0",
"name": "ABC"
}, {
"id": "1",
"name": "DEF"
}],
"13Jan2013": [{
"id": "0",
"name": "PQR"
}, {
"id": "1",
"name": "xyz"
}]
};
Same post is there on same site BUT here in dictionary json array key is dynamic.Here it is date ie 12Jan2013.It can be any date.It is not static.I have searched for that but didn't get solution.
How to iterate over such a json array?
AND How to print json array as in same formate shown above?
EDIT
Here is my real code.And i shown a comment in following code where i wanted to iterate data ie jsonData var in getWeatherDataForCities callback
var arrAllrecords = [];
var arrCityrecordForADay = [];
function getWeatherDataForCities(cityArray, callback){
var toDaysTimestamp = Math.round((new Date()).getTime() / 1000) - (24*60*60);
for(var i in cityArray){
for(var j=1; j<=1; j++){
var jsonurl = "http://api.openweathermap.org/data/2.5/history/city?q="+cityArray[i]+"&dt="+toDaysTimestamp;
$.ajax({
url: jsonurl,
dataType: "jsonp",
mimeType: "textPlain",
crossDomain: true,
contentType: "application/json; charset=utf-8",
success: function(data){
var arrCityRecordForDay = [];
/*arrCityrecordForADay.push(data.list[0].city.name);
arrCityrecordForADay.push(data.list[0].weather[0].description);
arrCityrecordForADay.push(timeConverter(data.list[0].dt));
arrCityrecordForADay.push(data.list[0].main.temp);
arrCityrecordForADay.push(data.list[0].main.humidity);
arrCityrecordForADay.push(data.list[0].main.pressure)
arrCityrecordForADay.push(data.list[0].wind.speed);*/
//'{"pets":[{"name":"jack"},{"name":"john"},{"name":"joe"}]}';
arrCityRecordForDay.push(
{"cityName" : data.list[0].city.name},
{"weather" : data.list[0].weather[0].description}
);
var tempId = data.list[0].city.name+"-"+timeConverter(data.list[0].dt);
arrCityrecordForADay.push(
{tempId : arrCityRecordForDay}
);
if(((arrCityrecordForADay.length)) === cityArray.length) {
callback(arrCityrecordForADay);
}
} });
toDaysTimestamp = toDaysTimestamp - (24*60*60);
}
}
}
$(document ).ready(function() {
var cityArray = new Array();
cityArray[0] = "pune";
getWeatherDataForCities(cityArray, function(jsonData) {
// Here I want to iterate jsonData
});
});
Use for-in...something like:
for (var i in dictionary) {
dictionary[i].forEach(function(elem, index) {
console.log(elem, index);
});
}
where the i would iterate through your dictionary object, and then you can use forEach for every json array in the dictionary(using dictionary[i])
With this code you'll get
Object {id: "0", name: "ABC"} 0
Object {id: "1", name: "DEF"} 1
Object {id: "0", name: "PQR"} 0
Object {id: "1", name: "xyz"} 1
You can tailor the forEach function definition(replacing the console.log bit) to do whatever you want with it.
DEMO
Edit: Doing the same thing using Object.keys
Object.keys(dictionary).forEach(function(key) {
dictionary[key].forEach(function(elem, index) {
console.log(elem, index);
});
});
Edit2: Given the somewhat complicated structure of your jsonData object, you could try using a (sort of) all-purpose function that would act on each type of component separately. I've probably missed a few cases, but maybe something like:
function strung(arg) {
var ret = '';
if (arg instanceof Array) {
arg.forEach(function(elem, index) {
ret += strung(elem) + ',';
});
} else if (arg instanceof Object) {
Object.keys(arg).forEach(function(key) {
ret += key + ': /' + strung(arg[key]) + '/';
});
} else if (typeof arg === "string" || typeof arg === "number") {
ret = arg;
}
return ret;
}
document.body.innerHTML = strung(jsonData);
DEMO
Please note that yours is just a JavaScript array object. To make it simple to understand, you can iterate over it like this:
for (var i in dictionary) {
// do something with i
// here i will contain the dates
for (n = 0; n < dictionary[i].length; n++) {
// do something with the inner array of your objects
// dictionary[i][n].id contains the "id" of nth object in the object i
// dictionary[i][n].name contains the "name" of nth object in the object i
}
}
See this fiddle: http://jsfiddle.net/Ke8F5/
The iteration looks like this:
12Jan2013 : (id = 0, name = ABC) (id = 1, name = DEF)
13Jan2013 : (id = 0, name = PQR) (id = 1, name = XYZ)
You can use a for loop.
for (var i in json) {
...
}
Then, i is the current key, so, you can acess json[ i ] and get the data to the corresponding index.
And then, if you need to iterate over inner elements, you can do the same thing.
You can use for ... in but you should combine it with hasOwnProperty or you'll find yourself iterating over inherited properties likely breaking your code.
for (var key in object) {
if (object.hasOwnProperty(key)) {
// Do stuff.
}
}

Javascript: Parse array like search querystring

This is a string i have in my javascript
var searchString = City=20&Region=67&&Interests[8]=8&Interests[13]=13&Interests[4]=4&Duration[1]=Full+Day+Tour&Duration[3]=Evening+Tour&Duration[5]=2+Day+Short+Break&Departs[Fri]=Fri&Departs[Sat]=Sat&Departs[Sun]=Sun&TourLanguages=1&action_doSearch=Update
and i have a function
function loadDataFrom(request){
//if request if empty then return
if(request == "")
return;
var request = decodeURIComponent(request);
//if we get there then its likely we have a search query to process
var searchCriteria = request.split('&');
var hash = {};
for(var i = 0; i < searchCriteria.length; i++) {
var val = searchCriteria[i].split('=');
//we can safely ignore the "view" and 'action_doSearch' becuase they are not searched on
if(unescape(val[0]) === 'view' || unescape(val[0]) === 'action_doSearch')
continue;
//filter objects without any values
if(val[1] != '')
//add the names and values to our object hash
hash[unescape(val[0])] = unescape(val[1]);
}
//iterate over the hash objects and apply the current selection
$.each(hash, function(index, value) {
switch (index) {
case 'City':
case 'Region':
case 'TourLanguages':
//do stuff;
break;
case 'Duration[]':
case 'Departs[]':
//do something esle
default:
break;
}
});
};
that parses the URL parameters into an objecct hash with the following values.
City: "20"
Region: "67"
Departs[Fri]: "Fri"
Departs[Sat]: "Sat"
Departs[Sun]: "Sun"
Duration[1]: "Full+Day+Tour"
Duration[3]: "Evening+Tour"
Duration[5]: "2+Day+Short+Break"
Interests[4]: "4"
Interests[8]: "8"
Interests[13]: "13"
TourLanguages: "1"
but what i'd really like to do is to seperate the url into array like values like so
City: "20"
Region: "67"
Departs: ["Fri","Sat","Sun"]
Duration: ["Full+Day+Tour", "Evening+Tour", "2+Day+Short+Break"]
Interests: ["4", "8", "13"]
TourLanguages: "1"
Any help/pointers on this problem is greatly appreciated. Thanks in Advance
For something like this, to make it easier on myself I would write a RegExp to get the parts, then do some if logic to decide how to construct the Object.
var searchString = "City=20&Region=67&&Interests[8]=8&Interests[13]=13&Interests[4]=4&Duration[1]=Full+Day+Tour&Duration[3]=Evening+Tour&Duration[5]=2+Day+Short+Break&Departs[Fri]=Fri&Departs[Sat]=Sat&Departs[Sun]=Sun&TourLanguages=1&action_doSearch=Update",
o = {};
('&' + searchString)
.replace(
/&([^\[=&]+)(\[[^\]]*\])?(?:=([^&]*))?/g,
function (m, $1, $2, $3) {
if ($2) {
if (!o[$1]) o[$1] = [];
o[$1].push($3);
} else o[$1] = $3;
}
);
o; /*
{
"City": "20",
"Region": "67",
"Interests": ["8", "13", "4"],
"Duration": ["Full+Day+Tour", "Evening+Tour", "2+Day+Short+Break"],
"Departs": ["Fri", "Sat", "Sun"],
"TourLanguages": "1",
"action_doSearch": "Update"
} */
I would do it this way:
var str = 'City=20&Region=67&&Interests[8]=8&Interests[13]=13&Interests[4]=4&Duration[1]=Full+Day+Tour&Duration[3]=Evening+Tour&Duration[5]=2+Day+Short+Break&Departs[Fri]=Fri&Departs[Sat]=Sat&Departs[Sun]=Sun&TourLanguages=1&action_doSearch=Update',
strsplit = str.split(/&+/),
o = {};
for (var i = 0, l = strsplit.length; i < l; i++) {
var r = strsplit[i].match(/^([^=\[\]]+)(?:\[[^\]]+\])?=(.*)$/);
if (o[r[1]] === undefined) {
o[r[1]] = r[2];
} else if (o[r[1]].push) {
o[r[1]].push(r[2]);
} else {
o[r[1]] = [o[r[1]], r[2]];
}
}
This is a perfect scenario to use Map-Reduce and I recommend you to use underscore.js to implement something simple, elegant and more readable solution.
var m = _.map(searchString.split('&'), function (item) {
var parts = item.split('='), names = parts[0].split('[');
return [names[0], parts[1]];
});
var result = _.reduce(m, function(memo, item){
var key = item[0], value = item[1];
if(memo[key] === undefined) memo[key] = [value]
else memo[key].push(value)
return memo;
}, {});
console.log(result);

Categories

Resources