Fastest way to flatten / un-flatten nested JSON objects
Solution 1:
function _isObject(value) {
return typeof(value) === 'object' && value !== null;
}
function _isArray(value) {
return Array.isArray(value);
}
function _isString(value) {
return typeof(value) === 'string';
}
function _isUndefined(value) {
return typeof(value) === 'undefined';
}
function _isNumber(value) {
return typeof(value) === 'number';
}
function _isBoolean(value) {
return typeof(value) === 'boolean';
}
function _isDate(value) {
return value instanceof Date;
}
function DataObjectParser($data){
this._data = $data || {};
}
DataObjectParser.prototype.set = function($path, $value) {
if(!$path || $path==='') return void 0;
var _self = this;
var re = /[\$\w-|]+|\[\]|([^\[[\w]\]]|["'](.*?)['"])/g;
var pathList = $path.match(re);
var parent = this._data;
var parentKey;
var grandParent = null;
var grandParentKey = null;
var addObj = function($obj, $key, $data) {
if($key === '[]') {
$obj.push($data);
} else {
$obj[$key] = $data;
}
};
while(pathList.length > 0) {
parentKey = pathList.shift().replace(/["']/g, '');
if (!isNaN(+parentKey) || parentKey === "[]") {
if(!_isArray(parent) ) {
parent = [];
addObj(grandParent, grandParentKey, parent);
}
} else if (_isString(parentKey)) {
if(!_isObject(parent)) {
parent = {};
addObj(grandParent, grandParentKey, parent);
}
}
grandParent = parent;
grandParentKey = parentKey;
parent = parent[parentKey];
}
addObj(grandParent, grandParentKey, $value);
return this;
};
DataObjectParser.prototype.get = function($path) {
var data = this._data;
var regex = /[\$\w-|]+|\[\]|([^\[[\w]\]]|["'](.*?)['"])/g;
if (!$path) return void 0;
var paths = $path.match(regex);
while (data !== null && paths.length > 0) {
if(data.propertyIsEnumerable(paths[0].replace(/"/g, ''))){
data = data[paths.shift().replace(/"/g, '')];
}
else{
return undefined;
}
}
return data;
};
DataObjectParser.prototype.data = function($data) {
if(!_isUndefined($data)) {
this._data = $data;
return this;
}
return this._data;
};
DataObjectParser.transpose = function($flat) {
var parser = (new DataObjectParser());
for(var n in $flat) {
if($flat[n]!==undefined) {
parser.set(n, $flat[n]);
}
}
return parser;
};
DataObjectParser.untranspose = function($structured) {
$structured = $structured || {};
var structuredData = $structured._data || $structured;
var traverse = function($data, $isIndex) {
var result = [];
var createMapHandler = function($name, $data) {
return function($item, $i) {
var name = $name;
if((/\./).test($name)) name = '["'+name+'"]';
$item.key.unshift(name+".");
return {
key: $item.key,
data: $item.data
};
};
};
for(var name in $data) {
var modifiedName;
if($isIndex) modifiedName = "["+name+"]";
else modifiedName = name;
if(_isString($data[name]) || _isNumber($data[name]) || $data[name]===null || _isBoolean($data[name]) || _isDate($data[name])) {
if((/\./).test(name)) modifiedName = '["'+name+'"]';
result.push({
key: [modifiedName],
data: $data[name]
});
}
else if(_isArray($data[name])) {
var subArray = traverse($data[name],true);
result = result.concat(subArray.map(createMapHandler(modifiedName, $data)));
}
else if(_isObject($data[name])) {
var subObject = traverse($data[name],false);
result = result.concat(subObject.map(createMapHandler(modifiedName, $data)));
}
}
return result;
};
var flatArray = traverse(structuredData,false);
var flatObj = {};
flatArray.every(function($item) {
for(var i = 0;i<$item.key.length-1;i++){
var name = $item.key[i];
var nextName = $item.key[i+1];
if((/^\[/).test(nextName)){
$item.key[i] = name.replace(/\.$/,"");
}
}
flatObj[$item.key.join("")] = $item.data;
return true;
});
return flatObj;
};
var d = new DataObjectParser();
d.set("User.caravan.personel.leader","Travis");
d.set("User.caravan.personel.cook","Brent");
d.set("User.location.rooms[0]", "kitchen");
d.set("User.location.rooms[1]", "bathroom");
d.set("User.location.rowArr[0][0].Name", "Jun 00");
d.set("User.location.rowArr[0][0].Age", 19);
d.set("User.location.rowArr[0][1].Name", "Jun 01");
d.set("User.location.rowArr[0][1].Age", 20);
d.set("User.location.rowArr[1][0].Name", "Jun 10");
d.set("User.location.rowArr[1][0].Age", 21);
d.set("User.location.rowArr[1][1].Name", "Jun 11");
d.set("User.location.rowArr[1][1].Age", 22);
var obj1 = d.data();
console.log(obj1);
var flat = DataObjectParser.untranspose(obj1);
var obj2 = DataObjectParser.transpose(flat).data();
console.log(flat);
console.log(obj2);
Solution 2:
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[""];
}
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));
let obj1 = {
"User": {
"UserID": 999,
"Username": "Jun",
"IsEnabled": true,
"PermissionArr": {
"52": ["VIEW", "UPDATE"],
"53": ["VIEW", "UPDATE"],
"54": [{"Name": "View", "Status": true}, {"Name": "Create", "Status": false}],
},
"CreditCardArr": [
{"Num": "12345"},
{"Num": "6789"},
],
"RowArr": [
[{"Name": "Jun 00"}, {"Name": "Jun 01"}],
[{"Name": "Jun 10"}, {"Name": "Jun 11"}],
],
}
};
let data1 = flatten(obj1);
let obj2 = unflatten(data1);
console.log(obj2);
Reference:
https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
https://stackoverflow.com/questions/7793811/convert-javascript-dot-notation-object-to-nested-object