Fastest way to flatten / un-flatten nested JSON objects
Solution 1:
// Reference:
// https://github.com/henrytseng/dataobject-parser
/**
* Dependencies
*/
// True if Object
function _isObject(value) {
return typeof(value) === 'object' && value !== null;
}
// True if Array
function _isArray(value) {
return Array.isArray(value);
}
/// True if type is string
function _isString(value) {
return typeof(value) === 'string';
}
// True if undefined
function _isUndefined(value) {
return typeof(value) === 'undefined';
}
// True if Number
function _isNumber(value) {
return typeof(value) === 'number';
}
// True if Boolean
function _isBoolean(value) {
return typeof(value) === 'boolean';
}
// True if Date object
function _isDate(value) {
return value instanceof Date;
}
function DataObjectParser($data){
this._data = $data || {};
}
/**
* Given a dot deliminated string set will create an object
* based on the structure of the string with the desired value
*
* @param {[String} $path path indicating where value should be placed
* @param {Mixed} $value the value desired to be set at the location determined by path
*/
DataObjectParser.prototype.set = function($path, $value) {
if(!$path || $path==='') return void 0;
var _self = this;
var re = /[\$\w-|]+|\[\]|([^\[[\w]\]]|["'](.*?)['"])/g;
// parse $path on dots, and brackets
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, '');
// Number, treat it as an array
if (!isNaN(+parentKey) || parentKey === "[]") {
if(!_isArray(parent) /* prevent overwritting */ ) {
parent = [];
addObj(grandParent, grandParentKey, parent);
}
// String, treat it as a key
} else if (_isString(parentKey)) {
if(!_isObject(parent)) {
parent = {};
addObj(grandParent, grandParentKey, parent);
}
}
// Next
grandParent = parent;
grandParentKey = parentKey;
parent = parent[parentKey];
}
addObj(grandParent, grandParentKey, $value);
return this;
};
/**
* Returns the value defined by the path passed in
*
* @param {String} $path string leading to a desired value
* @return {Mixed} a value in an object
*/
DataObjectParser.prototype.get = function($path) {
var data = this._data;
var regex = /[\$\w-|]+|\[\]|([^\[[\w]\]]|["'](.*?)['"])/g;
//check if $path is truthy
if (!$path) return void 0;
//parse $path on dots and brackets
var paths = $path.match(regex);
//step through data object until all keys in path have been processed
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;
};
/**
* "Transposes" data; receives flat data and returns structured
*
* @param {Object} $data Structured object
* @return {DataObjectParser} An instance of a DataObjectParser
*/
DataObjectParser.transpose = function($flat) {
var parser = (new DataObjectParser());
for(var n in $flat) {
if($flat[n]!==undefined) {
parser.set(n, $flat[n]);
}
}
return parser;
};
/**
* "Untransposes" data object; opposite of transpose
*
* @param {Mixed} $structured A Object or a DataObjectParser
* @return {Object} Flat object
*/
DataObjectParser.untranspose = function($structured) {
//check to see if $structured is passed
$structured = $structured || {};
//handles if an object or a dataObjectParser is passed in
var structuredData = $structured._data || $structured;
var traverse = function($data, $isIndex) {
var result = [];
var createMapHandler = function($name, $data) {
return function($item, $i) {
var name = $name;
//check if $name is a key of form "hello.world"
if((/\./).test($name)) name = '["'+name+'"]';
//add name to $item.key
$item.key.unshift(name+".");
//return $item.key with updated key
return {
key: $item.key,
data: $item.data
};
};
};
for(var name in $data) {
var modifiedName;
// check if current name is an arrays index
if($isIndex) modifiedName = "["+name+"]";
else modifiedName = name;
// check if current name is linked to a value
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]
});
}
// check if current name is an array
else if(_isArray($data[name])) {
// tell traverse next name is an array's index
var subArray = traverse($data[name],true);
result = result.concat(subArray.map(createMapHandler(modifiedName, $data)));
}
//check if current name is an object
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) {
//check for any dots followed by brackets and remove the dots
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(/\.$/,"");
}
}
//join all the keys in flatArray to form one key
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:
// Reference:
// https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
// https://stackoverflow.com/questions/24833379/why-and-when-do-we-need-to-flatten-json-objects
// https://stackoverflow.com/questions/7793811/convert-javascript-dot-notation-object-to-nested-object
// https://github.com/henrytseng/dataobject-parser
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(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