I need to convert "flat object" like this (input data):
{
'prop1': 'value.1',
'prop2-subprop1': 'value.2.1',
'prop2-subprop2': 'value.2.2',
}
to immersion object like this (output data):
{
'prop1': 'value.1',
'prop2': {
'subprop1': 'value.2.1',
'subprop2': 'value.2.2'
}
}
Of course solution have to be prepare for no-limit deep level.
My solution does not work:
var inputData = {
'prop1': 'value.1',
'prop2-subprop1': 'value.2.1',
'prop2-subprop2': 'value.2.2',
};
function getImmersionObj(input, value) {
var output = {};
if ($.type(input) === 'object') { // first start
$.each(input, function (prop, val) {
output = getImmersionObj(prop.split('-'), val);
});
} else if ($.type(input) === 'array') { // recursion start
$.each(input, function (idx, prop) {
output[prop] = output[prop] || {};
output = output[prop];
});
}
return output;
}
console.log(getImmersionObj(inputData)); // return empty object
Can you help me find the problem in my code or maybe you know another one, better algorithm for conversion like my?
You could use a function for spliting the path to the value and generate new objects for it.
function setValue(object, path, value) {
var way = path.split('-'),
last = way.pop();
way.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = value;
}
var object = { 'prop1': 'value.1', 'prop2-subprop1': 'value.2.1', 'prop2-subprop2': 'value.2.2' };
Object.keys(object).forEach(function (key) {
if (key.indexOf('-') !== -1) {
setValue(object, key, object[key]);
delete object[key];
}
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var input = {
"property1": "value1",
"property2.property3": "value2",
"property2.property7": "value4",
"property4.property5.property6.property8": "value3"
}
function addProp(obj, path, pathValue) {
var pathArray = path.split('.');
pathArray.reduce(function (acc, value, index) {
if (index === pathArray.length - 1) {
acc[value] = pathValue;
return acc;
} else if (acc[value]) {
if (typeof acc[value] === "object" && index !== pathArray.length - 1) {
return acc[value];
} else {
var child = {};
acc[value] = child;
return child;
}
} else {
var child = {};
acc[value] = child;
return child;
}
}, obj);
}
var keys = Object.keys(input);
var output = {};
keys.forEach(function (k) {
addProp(output, k, input[k]);
});
console.log(output);
Related
Suppose I have an object with depth-N like:
food = {
'Non-Animal': {
'Plants' : {
'Vegetables': {
...
}
},
'Minerals' : {
...
}
},
'Animal': {
...
}
}
And I want to add in this object the category 'Fruits', but I have to search the object where 'Plants' are and then add it. So I don't want to do in one statement:
food['Non-Animal']['Plants']['Fruits'] = {};
Since I want to search first where it belongs.
How can I add the fruits category to the object while iterating through it? What I have so far is:
addCategory(food, category, parent);
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name) {
obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
addCategory(p, category, parent);
} else {
}
}
}
How can I fix this routine to do this or is there a better way to do this?
If I'm understanding you correctly, I think you'd want your function to define a variadic parameter that takes individual names of the path you wish to traverse and create if necessary.
Using .reduce() for this makes it pretty easy.
const food = {
'Non-Animal': {
'Plants': {
'Vegetables': {}
},
'Minerals': {}
},
'Animal': {}
}
console.log(addCategory(food, "Non-Animal", "Plants", "Fruits"));
console.log(addCategory(food, "Non-Animal", "Minerals", "Gold"));
function addCategory(obj, ...path) {
return path.reduce((curr, name) => {
if (!curr) return null;
if (!curr[name]) return (curr[name] = {});
return curr[name];
// More terse but perhaps less readable
// return curr ? curr[name] ? curr[name] : (curr[name]={}) : null;
}, obj);
}
console.log(JSON.stringify(food, null, 2));
Looks fine. However you might want to terminate after adding the prop:
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name){
return obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
if(addCategory(p, category, parent)) return true;
}
}
}
I see only one mistake: The recursive call of addCategory cannot find the parent-variable, cause it's called parent_name in your scope.
var food = {
'Non-Animal': {
'Plants' : {
'Vegetables': {
}
},
'Minerals' : {}
},
'Animal': {}
}
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name){
obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
addCategory(p, category, parent_name);
} else {
}
}
}
console.log(food);
addCategory(food, 'Fruits', 'Plants');
console.log(food);
You can use reduce to create function that will take key as string and object as value that you want to assign to some nested object.
var food = {"Non-Animal":{"Plants":{"Vegetables":{}},"Minerals":{}},"Animal":{}}
function add(key, value, object) {
key.split('.').reduce(function(r, e, i, arr) {
if(r[e] && i == arr.length - 1) Object.assign(r[e], value);
return r[e]
}, object)
}
add('Non-Animal.Plants', {'Fruits': {}}, food)
console.log(food)
If I have a JSON, for example:
{
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
}
I want to access and modify some data but i don't know which one.
I'll have an array of keys like this:
["test2","test22"] and a value: "change to this".
I want to change the myjson.test2.test22 data to "change to this".
Is there some simple and elegant way to do this?
Note that I don't know what the array's content will be, so I cant use the myjson.test2.test22 access method.
Try this
function setDeep(obj, props, value) {
var cur = obj,
prop;
while ((prop = props.shift()) && props.length) {
cur = cur[prop]
}
cur[prop] = value
return obj;
}
var obj = {
"test1": {
"test11": "someting",
"test12": "something else"
},
"test2": {
"test21": "asdasd",
"test22": "qwasd"
}
}
setDeep(obj, ['test1', 'test12'], 'new value');
console.log(obj)
You could use Array#reduce and walk the path. Then assign the value to the object with the last key.
var object = { "test1": { "test11": "someting", "test12": "something else" }, "test2": { "test21": "asdasd", "test22": "qwasd" } },
path = ["test2", "test22"],
lastKey = path.pop();
path.reduce(function (o, k) {
return o[k];
}, object)[lastKey] = 'change to this';
console.log(object);
For unknow properties, i suggets to make a check befor and use a default object then.
var object = { "test1": { "test11": "someting", "test12": "something else" }, "test2": { "test21": "asdasd", "test22": "qwasd" } },
path = ["test2", "test22", "42"],
lastKey = path.pop();
path.reduce(function (o, k) {
(o[k] !== null && typeof o[k] === 'object') || (o[k] = {});
return o[k];
}, object)[lastKey] = 'change to this';
console.log(object);
Following is a recursive function the traverses the keys of an object until the matching key (last one in the array) is found then it's value is changed and the changed object is returned. base case is when the array of keys is empty. Returns false if the key is not found
given an object named obj
var obj = {
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
function changeAkey(object, arrayOfKeys, value){
if(arrayOfKeys.length == 0)
return value;
else{
if(!object[arrayOfKeys[0]])
return false;
else
object[arrayOfKeys[0]] = changeAkey(object[arrayOfKeys[0]], arrayOfKeys.slice(1), value);
return object;
}
}
so changeAkey(obj, ["test2", "test22"], value) would do the job
I would start by checking if the property exists and if it does change the data. This way it won't throw an unspecific error.
var keys = ["key1", "key2"];
var myObj = {
"random1": {
"random2": "someting",
"random3": "something else"
},
"key1": {
"random4": "asdasd",
"key2": "qwasd"
};
}
var hasKeys = myObj[keys[0]][keys[1]] || null;
if(hasKeys) {
myObj[keys[0]][keys[1]] = "some data";
} else {
console.log('property not found');
}
I would propose to serialize your object and use String replace(). It works for any depth and simple code.
var objStr = JSON.stringify(obj);
changes.forEach(function(item){
objStr = objStr.replace(item, to);
})
obj = JSON.parse(objStr);
var obj = {
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
}
var changes = ["test11", "test21"];
var to = 'change to this';
var objStr = JSON.stringify(obj);
changes.forEach(function(item){
objStr = objStr.replace(item, to);
})
obj = JSON.parse(objStr);
console.log(obj)
var obj = {
"test1":{
"test11":"someting",
"test12":"something else"
},
"test2":{
"test21":"asdasd",
"test22":"qwasd"
}
};
var keys = ["test2","test22"];
function update_obj(obj, keys, value) {
for (var i = 0; i < keys.length - 1; i++) {
obj = obj[keys[i]];
}
obj[keys[keys.length - 1]] = value;
}
update_obj(obj, keys, "hi");
console.log(obj);
I would like to list all paths of object that lead to leafs
Example:
var obj = {
a:"1",
b:{
foo:"2",
bar:3
},
c:[0,1]
}
Result:
"a","b.foo","b.bar", "c[0]","c[1]"
I would like to find simple and readable solution, best using lodash.
Here is a solution that uses lodash in as many ways as I can think of:
function paths(obj, parentKey) {
var result;
if (_.isArray(obj)) {
var idx = 0;
result = _.flatMap(obj, function (obj) {
return paths(obj, (parentKey || '') + '[' + idx++ + ']');
});
}
else if (_.isPlainObject(obj)) {
result = _.flatMap(_.keys(obj), function (key) {
return _.map(paths(obj[key], key), function (subkey) {
return (parentKey ? parentKey + '.' : '') + subkey;
});
});
}
else {
result = [];
}
return _.concat(result, parentKey || []);
}
Edit: If you truly want just the leaves, just return result in the last line.
Doesn't use lodash, but here it is with recursion:
var getLeaves = function(tree) {
var leaves = [];
var walk = function(obj,path){
path = path || "";
for(var n in obj){
if (obj.hasOwnProperty(n)) {
if(typeof obj[n] === "object" || obj[n] instanceof Array) {
walk(obj[n],path + "." + n);
} else {
leaves.push(path + "." + n);
}
}
}
}
walk(tree,"tree");
return leaves;
}
Based on Nick answer, here is a TS / ES6 imports version of the same code
import {isArray,flatMap,map,keys,isPlainObject,concat} from "lodash";
// See https://stackoverflow.com/a/36490174/82609
export function paths(obj: any, parentKey?: string): string[] {
var result: string[];
if (isArray(obj)) {
var idx = 0;
result = flatMap(obj, function(obj: any) {
return paths(obj, (parentKey || '') + '[' + idx++ + ']');
});
} else if (isPlainObject(obj)) {
result = flatMap(keys(obj), function(key) {
return map(paths(obj[key], key), function(subkey) {
return (parentKey ? parentKey + '.' : '') + subkey;
});
});
} else {
result = [];
}
return concat(result, parentKey || []);
}
Feeding that object through this function should do it I think.
recursePaths: function(obj){
var result = [];
//get keys for both arrays and objects
var keys = _.map(obj, function(value, index, collection){
return index;
});
//Iterate over keys
for (var key in keys) {
//Get paths for sub objects
if (typeof obj[key] === 'object'){
var paths = allPaths(obj[key]);
for (var path in paths){
result.push(key + "." + path);
}
} else {
result.push(key);
}
}
return result;
}
Here is my function. It generates all possible paths with dot notation, assuming there are no property names containing spaces
function getAllPathes(dataObj) {
const reducer = (aggregator, val, key) => {
let paths = [key];
if(_.isObject(val)) {
paths = _.reduce(val, reducer, []);
paths = _.map(paths, path => key + '.' + path);
}
aggregator.push(...paths);
return aggregator;
};
const arrayIndexRegEx = /\.(\d+)/gi;
let paths = _.reduce(dataObj, reducer, []);
paths = _.map(paths, path => path.replace(arrayIndexRegEx, '[$1]'));
return paths;
}
Here's my solution. I only did it because I felt the other solutions used too much logic. Mine does not use lodash since I don't think it would add any value. It also doesn't make array keys look like [0].
const getAllPaths = (() => {
function iterate(path,current,[key,value]){
const currentPath = [...path,key];
if(typeof value === 'object' && value != null){
return [
...current,
...iterateObject(value,currentPath)
];
}
else {
return [
...current,
currentPath.join('.')
];
}
}
function iterateObject(obj,path = []){
return Object.entries(obj).reduce(
iterate.bind(null,path),
[]
);
}
return iterateObject;
})();
If you need one where the keys are indexed using [] then use this:
const getAllPaths = (() => {
function iterate(path,isArray,current,[key,value]){
const currentPath = [...path];
if(isArray){
currentPath.push(`${currentPath.pop()}[${key}]`);
}
else {
currentPath.push(key);
}
if(typeof value === 'object' && value != null){
return [
...current,
...iterateObject(value,currentPath)
];
}
else {
return [
...current,
currentPath.join('.')
];
}
}
function iterateObject(obj,path = []){
return Object.entries(obj).reduce(
iterate.bind(null,path,Array.isArray(obj)),
[]
);
}
return iterateObject;
})();
const allEntries = (o, prefix = '', out = []) => {
if (_.isObject(o) || _.isArray(o)) Object.entries(o).forEach(([k, v]) => allEntries(v, prefix === '' ? k : `${prefix}.${k}`, out));
else out.push([prefix, o]);
return out;
};
Array are returned as .0 or .1 that are compatible with _.get of lodash
const getAllPaths = (obj: object) => {
function rKeys(o: object, path?: string) {
if (typeof o !== "object") return path;
return Object.keys(o).map((key) =>
rKeys(o[key], path ? [path, key].join(".") : key)
);
}
return rKeys(obj).toString().split(",").filter(Boolean) as string[];
};
const getAllPaths = (obj) => {
function rKeys(o, path) {
if (typeof o !== "object") return path;
return Object.keys(o).map((key) =>
rKeys(o[key], path ? [path, key].join(".") : key)
);
}
return rKeys(obj).toString().split(",").filter(Boolean);
};
const test = {
a: {
b: {
c: 1
},
d: 2
},
e: 1
}
console.log(getAllPaths(test))
I have a JSON OBJECT similar to following
{
"kay1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33":"value33"
}
}
I want to replace that with JSON ARRAY as follows
[
"value1",
"value2",
[
"value31",
"value32",
"value33"
]
]
My motivation to change the the JSON OBJECT to JSON ARRAY is it takes less amount of network traffic, getting the value from ARRAY is efficient than OBJECT, etc.
One problem I face is the readability of the ARRAY is very less than OBJECT.
Is there any way to improve the readability?
Here you go, I have written a function which will convert all instances of object to array and give you the result you are expecting.
var objActual = {
"key1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33": {
"key331" : "value331",
"key332" : "value332"
}
}
};
ObjectUtil = {
isObject : function(variable) {
if(Object.prototype.toString.call(variable) === '[object Object]') {
return true;
}
return false;
},
convertToArray : function(obj) {
var objkeys = Object.keys(obj);
var arr = [];
objkeys.forEach(function(key) {
var objectToPush;
if(ObjectUtil.isObject(obj[key])) {
objectToPush = ObjectUtil.convertToArray(obj[key]);
} else {
objectToPush = obj[key];
}
arr.push(objectToPush);
});
return arr;
}
};
var result = ObjectUtil.convertToArray(objActual);
console.log(result);
In my opinion, it should be:
{
"kay1":"value1",
"key2":"value2",
"key3":["value31", "value32", "value33"]
}
Using the init method is time critical. So use a scheme or assign static JSON to Storage.keys and assign your bulk data array to store.data. You can use store.get("key3.key31") after that. http://jsfiddle.net/2o411k00/
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
var data = {
"kay1":"value1",
"key2":"value2",
"key3":{
"key31":"value31",
"key32":"value32",
"key33":"value33"
}
}
var Storage = function(data){
this.rawData = data;
return this;
}
Storage.prototype.init = function(){
var self = this;
var index = 0;
var mp = function(dat, rootKey){
var res = Object.keys(dat).map(function(key, i) {
var v = dat[key];
if (typeof(v) === 'object'){
mp(v, key);
} else {
self.data.push(v);
var nspace = rootKey.split(".").concat([key]).join(".");
self.keys[nspace] = index++;
}
});
}
mp(this.rawData, "");
}
Storage.prototype.get = function(key){
return this.data[this.keys[key]];
};
Storage.prototype.data = [];
Storage.prototype.keys = {};
var store = new Storage(data);
console.log(data);
store.init();
console.log("keys", store.keys);
console.log("data", store.data);
console.log("kay1=", store.get(".kay1"));
console.log("key2=", store.get(".key2"));
console.log("key3.key31=", store.get("key3.key31"));
console.log("key3.key32=",store.get("key3.key32"));
console.log("key3.key33=", store.get("key3.key33"));
I have an easy dot notation getter function and I would love to have a setter that works in the same way. Any ideas?
var person = {
name : {
first : 'Peter',
last : 'Smith'
}
};
// ---
var dotGet = function(str, obj) {
return str.split('.').reduce(function(obj, i) {
return obj[i];
}, obj);
};
var dotSet = function(str, value, obj) {
// updated, thx to #thg435
var arr = str.split('.');
while (arr.length > 1) {
obj = obj[arr.shift()];
}
obj[arr.shift()] = value;
return obj;
}
// returns `Peter`
var a = dotGet('person.name.first', person);
// should set `person.name.first` to 'Bob'
var b = dotSet('person.name.first', 'Bob', person);
var set = function (exp, value, scope) {
var levels = exp.split('.');
var max_level = levels.length - 1;
var target = scope;
levels.some(function (level, i) {
if (typeof level === 'undefined') {
return true;
}
if (i === max_level) {
target[level] = value;
} else {
var obj = target[level] || {};
target[level] = obj;
target = obj;
}
});
};
You can check out my expression compiler that does what you need and more.
The usage is:
var scope = {};
set('person.name', 'Josh', scope);
scope.person.name === 'Josh'
Try this:
var dotSet = function(str, value, obj) {
var keys = str.split('.');
var parent = obj;
for (var i = 0; i < keys.length - 1; i++) {
var key = keys[i];
if (!(key in parent)) {
parent[key] = {};
parent = parent[key];
}
}
parent[keys[keys.length - 1]] = value;
}
var person = {};
dotSet('person.name.first', 'Bob', person);
It produces this object:
{ person: { name: { first: 'Bob' } } }