So I need a deepSet() function in my React application and I know there are dozens of libraries available for that. However, my requirements are beyond that of the standard deepSet(). My function has to be able to support Arrays, and multiple Arrays in the path.
Example, simple deepSet()
var obj = { one: { two: {three: 'a'}} };
deepSet(obj, 'one.two.three', 'yay');
// {one: {two: { three: 'yay' } } }
What I need it to support (and have working)
var obj = { one: { two: [{three: 'a'}, {three: 'a'}] } };
deepSet(obj, 'one.two[].three', 'yay');
// { one: { two: [{three: 'yay'}, {three: 'yay'}] }};
What I also need it to support, and DO NOT have working yet
var obj = { one: { two: [{three: [{four:'a'}, {four:'b'}]}, {three: [{four:'a'}, {four:'b'}]}]}};
deepSet(obj, 'one.two[].three[].four', 'yay');
// { one: { two: [{three: [{four:'yay'}, {four:'yay'}]}, {three: [{four:'yay'}, {four:'yay'}]}]}};
My problem is that I can't figure out how to get that next level of arrays and iterate over them. I think a recursive approach is best here, but I can't figure that out to handle 2 or more Arrays. I've spent a few hours on this and am turning to SO for help.
Here's the algorithm I have so far, that supports 0 and 1 level of Arrays.
const deepSet = (object, path, value) => {
let fields = path.split('.');
for (let i=0; i<fields.length; i++) {
let f = fields[i];
if (f.indexOf("[]") > -1) {
let arrayfield = f.replace('[]', '');
f = fields[++i];
object[arrayfield].forEach((el, idx) => {
if (isEmpty(el[f])) {
el[f]= {};
}
if (i === fields.length - 1) {
el[f] = value;
}
});
object = object[arrayfield];
}
else {
if (isEmpty(object[f])) {
object[f] = {};
}
if (i === fields.length - 1) {
object[f] = value;
}
object = object[f];
}
};
}
You need to walk through branches as you find these, so that each branch entry becomes a new deepSet.
Example
const deepSet = (obj, path, value) => {
const re = /(\.|\[\]\.)/g;
let i = 0, match = null;
while (match = re.exec(path)) {
const sep = match[0];
const {length} = sep;
const {index} = match;
obj = obj[path.slice(i, index)];
i = index + length;
if (1 < length) {
path = path.slice(i);
obj.forEach(obj => {
deepSet(obj, path, value);
});
return;
}
}
obj[path.slice(i)] = value;
};
var obj = { one: { two: [{three: [{four:'a'}, {four:'b'}]}, {three: [{four:'a'}, {four:'b'}]}]}};
deepSet(obj, 'one.two[].three[].four', 'yay');
This will produce the expected result:
{
"one": {
"two": [
{
"three": [
{
"four": "yay"
},
{
"four": "yay"
}
]
},
{
"three": [
{
"four": "yay"
},
{
"four": "yay"
}
]
}
]
}
}
Hope it helps or, at least, it gave you a hint đź‘‹
You could take a recursive approach
const
deepSet = (object, path, value) => {
let [key, ...keys] = path.split('.');
if (key.endsWith('[]')) {
object = object[key.slice(0, -2)];
Object.keys(object).forEach(k => {
if (keys.length) deepSet(object[k], keys.join('.'), value);
else object[k] = value;
});
} else {
if (keys.length) deepSet(object[key], keys.join('.'), value);
else object[key] = value;
}
},
object = { one: { two: [{ three: [{ four: 'a' }, { four: 'b' }] }, { three: [{ four: 'a' }, { four: 'b' }] }] } };
deepSet(object, 'one.two[].three[].four', 'yay');
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I have two identical objects with me
let a = {
title : "developer”,
startDate:{ month :’jan’}
}
let b = {
title :{
value: ""
} ,
startDate :{month:{value:””}}
}
i need to merge dynamically these two to get object like below
let c = {
title :{
value: "developer"
} ,
startDate:{
month:{ value:” jan”}}
}
You don't require object b because it's just a replica of object a with extra 'value' property.
You can traverse the complete a object and then deep copy the value in the b object.
I wrote a recursive method for this where you can traverse to the last level of the object and copy the value in another object.
function mergeObj(sourceObj, newObj) {
Object.keys(sourceObj).forEach(key => {
if (sourceObj[key] && typeof sourceObj[key] === 'object') {
newObj[key] = {};
mergeObj(sourceObj[key], newObj[key]);
} else {
// updating properties
newObj[key] = {};
newObj[key]['value'] = sourceObj[key];
}
});
}
let a = {
title : "developer",
startDate:{ month :'jan'}
};
let b = {};
mergeObj(a,b);
console.log(b);
You probably need to start by making both object have the same structure, and then run the deep merge. lodash's merge can help you with it
const newA = Object.entries(a).reduce((newObject, [key, value]) => ({
...newObject,
[key]: { value },
}, {}))
// newA looks now like
//
// {
// title: {
// value: "developer
// }
// }
let c = _.merge(a, b); // lodash merge for deep merge. Otherwise write your own
Here is a workaround for your problem:
let a = {
title : "developer",
startDate:{ month :'jan'}
}
let b = {
title :{
value: ''
} ,
startDate :{month:{value:''}}
}
var c = {};
c.startDate = {};
c.title = {};
c.startDate.month = {};
c.startDate.month.value = a.startDate.month;
c.title.value = a.title;
console.log("Merged object",c);
You can just implement a function that does this for you. Given your example:
let a = {
title: "developer",
startDate: { month: "jan" }
};
let b = {
title: {
value: ""
},
startDate: { month: { value: "" }}
};
You can use this to get the values:
const mergeObject = (a, b) => {
b.title.value = a.title;
b.startDate.month.value = a.startDate.month;
return b;
};
If you call now say let c = mergeObject(a, b) c will be
let c = {
title: {
value: "developer"
},
startDate: {
month: { value: "jan" }}
}
Of course this function can be modified to reflect your exact needs.
i Have an array that could be :
arr1 = ["node1","children1","children1.1","children1.1.1"]
or it could be
arr2 = ["node1","children1"]
and I want to make it in this json format :
const data_arr1 = [{
title: "Node 1",
childNodes: [
{ title: "Childnode 1" ,
childNodes: [
{
title: "Childnode 1.1",
childNodes: [
{ title: "Childnode 1.1.1" }
]
}
]
}
]
}];
var data_arr2 = {title:"node1",childNodes:{title:"children1"}}
I have do like that but i can't have the right format in iterative way :
BuildJson = (items) => {
const elements = items.split(",");
let result = {};
var children = []
result["title"] = elements[0];
elements.shift()
if(elements.length>1) {
for(var i=0;i<elements.length;i++){
elements.map((el,idx)=> {
children.push({title:el})
})
}
result["ChildNodes"] = children
}
Please how can I fix this algorithm ?
I suggest you to use recursive function.
I made you an example:
const t = ["lvl1", "lvl2", "lvl3"];
const r = (array) => {
if (array.length === 1) {
return {
title: array[0]
};
}
if (array.length > 1) {
return {
title: array[0],
childNode: r(array.slice(1))
};
}
};
r(t);
r(t) returned the following JSON:
{
"title": "lvl1",
"childNode": {
"title": "lvl2",
"childNode": {
"title": "lvl3"
}
}
}
const arr1 = ["node1","children1","children1.1","children1.1.1"]
const createArray = (arr, i = 0) => {
const obj = {
title: arr[i]
};
if (i < arr.length - 1) {
obj.childNodes = createArray(arr, ++i);
}
return [obj];
}
const newArr = createArray(arr1);
console.log(newArr);
i have a javascript object as follows
obj = {"account_id-0":null,"option_item_id-0":1,"value-0":"wer","account_id-1":null,"option_item_id-1":2,"value-1":"kkk","account_id-2":null,"option_item_id-2":3,"value-2":"qqqqq"
....
"account_id-n":null,"option_item_id-n":6,"value-n":"see"
}
From the above object, i need to create the following structure
{"0": {
account_id: null,
option_item_id: 1,
value: "wer"
},
"1": {
account_id: null,
option_item_id: 2,
value: "kkk"
},
"2": {
account_id: null,
option_item_id: 2,
value: "qqqqq"
},
.
.
.
"n": {
account_id: null,
option_item_id: 6,
value: "see"
}
}
Any idea on how to implement this?
You can iterate through the all the keys, and use Array#reduce to contruct the resultant object.
let obj = {
"account_id-0": null,
"option_item_id-0": 1,
"value-0": "wer",
"account_id-1": null,
"option_item_id-1": 2,
"value-1": "kkk",
"account_id-2": null,
"option_item_id-2": 3,
"value-2": "qqqqq",
"account_id-n": null,
"option_item_id-n": 6,
"value-0": "see"
};
let result = Object.keys(obj).reduce((res, item) => {
let [key, index] = item.split('-');
if (!res[index]) {
res[index] = {};
}
res[index][key] = obj[item];
return res;
}, {});
console.log(result);
var obj = {
"account_id-0": null,
"option_item_id-0": 1,
"value-0": "wer",
"account_id-1": null,
"option_item_id-1": 2,
"value-1": "kkk",
"account_id-2": null,
"option_item_id-2": 3,
"value-2": "qqqqq"
};
var props = [];
function getObj(ind) {
for (var p in props) {
if (ind === p) {
return props[p];
}
}
}
for (var prop in obj) {
var parts = prop.split('-');
var key = parts[0];
var indx = parts[1];
var tmp = getObj(indx);
if (tmp == undefined) {
var x = {};
x[indx] = {};
x[indx][key] = obj[prop];
props.push(x);
} else {
tmp[indx][key] = obj[prop];
}
}
console.log(props);
This should be the straight forward way of maniplulating the object array with simple split() function.
Try this:
var keys = Object.keys(obj), i = 0
var arr = [], o = {}
for(var k in keys) {
if(keys[k].match(/\d*$/m) == i) {
o[keys[k].replace(/-\d*$/m,'')] = obj[keys[k]]
} else {
i++
arr.push(o)
o = {}
}
}
Here I am using an array instead of an object with the keys "0", "1", " 2", ... "n". I think it's way more convenient.
I've got the following JSON string:
{
"Alarm":{
"Hello":48,
"World":3,
"Orange":1
},
"Rapid":{
"Total":746084,
"Fake":20970,
"Cancel":9985,
"Word": 2343
},
"Flow":{
"Support":746084,
"About":0,
"Learn":0
}
}
Then I load the above string and convert it to json object:
jsonStr = '{"Alarm":{"Hello":48,"World":3,"Orange":1},"Rapid":{"Total":746084,"Fake":20970,"Cancel":9985},"Flow":{"Support":746084,"About":0,"Learn":0}}';
var jsonObj = JSON.parse(jsonStr);
Now, how can I filter this json object by key name?
E.g., if the filter was "ange", the filtered object would be:
{
"Alarm":{
"Orange":1
}
}
If the filter was "flo", the filtered object would become:
{
"Flow":{
"Support":746084,
"About":0,
"Learn":0
}
}
And if the filter was "wor", the result would be:
{
"Alarm":{
"World": 3,
},
"Rapid":{
"Word": 2343
}
}
Is it possible to achieve this filtering using the filter method?
Beside the given solutions, you could use a recursive style to check the keys.
This proposal gives the opportunity to have more nested objects inside and get only the filtered parts.
function filterBy(val) {
function iter(o, r) {
return Object.keys(o).reduce(function (b, k) {
var temp = {};
if (k.toLowerCase().indexOf(val.toLowerCase()) !== -1) {
r[k] = o[k];
return true;
}
if (o[k] !== null && typeof o[k] === 'object' && iter(o[k], temp)) {
r[k] = temp;
return true;
}
return b;
}, false);
}
var result = {};
iter(obj, result);
return result;
}
var obj = { Alarm: { Hello: 48, "World": 3, Orange: 1 }, Rapid: { Total: 746084, Fake: 20970, Cancel: 9985, Word: 2343 }, Flow: { Support: 746084, About: 0, Learn: 0 }, test: { test1: { test2: { world: 42 } } } };
console.log(filterBy('ange'));
console.log(filterBy('flo'));
console.log(filterBy('wor'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create a function using reduce() and Object.keys() that will check key names with indexOf() and return the desired result.
var obj = {
"Alarm": {
"Hello": 48,
"World": 3,
"Orange": 1
},
"Rapid": {
"Total": 746084,
"Fake": 20970,
"Cancel": 9985,
"Word": 2343
},
"Flow": {
"Support": 746084,
"About": 0,
"Learn": 0
}
}
function filterBy(val) {
var result = Object.keys(obj).reduce(function(r, e) {
if (e.toLowerCase().indexOf(val) != -1) {
r[e] = obj[e];
} else {
Object.keys(obj[e]).forEach(function(k) {
if (k.toLowerCase().indexOf(val) != -1) {
var object = {}
object[k] = obj[e][k];
r[e] = object;
}
})
}
return r;
}, {})
return result;
}
console.log(filterBy('ange'))
console.log(filterBy('flo'))
console.log(filterBy('wor'))
With the filter method I think you mean the Array#filter function. This doesn't work for objects.
Anyway, a solution for your input data could look like this:
function filterObjects(objects, filter) {
filter = filter.toLowerCase();
var filtered = {};
var keys = Object.keys(objects);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (objects.hasOwnProperty(key) === true) {
var object = objects[key];
var objectAsString = JSON.stringify(object).toLowerCase();
if (key.toLowerCase().indexOf(filter) > -1 || objectAsString.indexOf(filter) > -1) {
filtered[key] = object;
}
}
}
return filtered;
}
I'm trying to flatten an object where the keys will be the full path to the leaf node. I can recursively identify which are the leaf nodes but stuck trying to construct the whole path.
Sample Input:
{
one: 1,
two: {
three: 3
},
four: {
five: 5,
six: {
seven: 7
},
eight: 8
},
nine: 9
}
Output:
{
one: 1,
'two.three': 3,
'four.five': 5,
'four.six.seven': 7,
'four.eight': 8,
nine: 9
}
You could use a recursive approch and collect the keys of the object. This proposal looks for arrays as well.
function getFlatObject(object) {
function iter(o, p) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k));
});
return;
}
path[p.join('.')] = o;
}
var path = {};
iter(object, []);
return path;
}
var obj = { one: 1, two: { three: 3 }, four: { five: 5, six: { seven: 7 }, eight: 8 }, nine: 9 },
path = getFlatObject(obj);
console.log(path);
Partial solution :
Give the input as a full path to the function and it gives you the respective output
var obj = {
one: 1,
two: {
three: 3
},
four: {
five: 5,
six: {
seven: 7
},
eight: 8
},
nine: 9
};
function deepFind(obj, path) {
var paths = path.split('.')
, current = obj
, i;
for (i = 0; i < paths.length; ++i) {
if (current[paths[i]] == undefined) {
return undefined;
} else {
current = current[paths[i]];
}
}
return current;
}
console.log(deepFind(obj, 'four.six.seven'))
Using newest JS features like Object spread and Object.entries it should be pretty easy:
function flatObj(obj, path = []) {
let output = {};
Object.entries(obj).forEach(([ key, value ]) => {
const nextPath = [ ...path, key ];
if (typeof value !== 'object') {
output[nextPath.join('.')] = value;
return;
}
output = {
...output,
...flatObj(value, nextPath)
};
});
}
Please note that this code is probably not the most optimal one as it copies the object each time we want to merge it. Treat it more as a gist of what would it look like, rather than a complete and final solution.
var obj = {
one: 1,
two: {
three: 3
},
four: {
five: 5,
six: {
seven: 7
},
eight: 8
},
nine: 9
};
function flatten(obj) {
var flatObj = {}
function makeFlat(obj, path) {
var keys = Object.keys(obj);
if (keys.length) {
keys.forEach(function (key) {
makeFlat(obj[key], (path ? path + "." : path) + key);
})
} else {
flatObj[path] = obj;
}
}
makeFlat(obj, "");
return flatObj;
}
console.log(flatten(obj));
A non fancy approach, internally uses recursion.
var x = { one:1,two:{three:3},four:{five: 5,six:{seven:7},eight:8},nine:9};
var res = {};
var constructResultCurry = function(src){ return constructResult(res,src); }
function constructResult(target, src) {
if(!src) return;
target[src.key] = src.val;
}
function buildPath(key, obj, overAllKey) {
overAllKey += (overAllKey ? "." : "") + key;
if(typeof obj[key] != "object") return { key : overAllKey, val : obj[key] };
Object.keys(obj[key]).forEach(function(keyInner) {
constructResultCurry(buildPath(keyInner, obj[key], overAllKey));
});
}
Object.keys(x).forEach(function(k){
constructResultCurry(buildPath(k, x, ""));
});
console.log(res);
You might simply do as follows;
var obj = {one: 1, two: {three: 3}, four: {five: 5, six: {seven: 7}, eight: 8}, nine: 9},
flatObj = (o,p="") => { return Object.keys(o)
.map(k => o[k] === null ||
typeof o[k] !== "object" ? {[p + (p ? ".":"") + k]:o[k]}
: flatObj(o[k],p + (p ? ".":"") + k))
.reduce((p,c) => Object.assign(p,c));
};
console.log(flatObj(obj));
I find a tiny JavaScript utility to access properties using path. It is called object-path and is an opensource project on GitHub.
To get attribute from an object:
objectPath.get(obj, "a.b");
to set attribute:
objectPath.set(obj, "a.b", value);
to remove an attribute:
objectPath.del(obj, "a.b");
So easy!!
You can achieve it by using this function:
const obj = {
one: 1,
two: {
three: 3
},
four: {
five: 5,
six: {
seven: 7
},
eight: 8
},
nine: 9
}
const flatObject = (obj, keyPrefix = null) =>
Object.entries(obj).reduce((acc, [key, val]) => {
const nextKey = keyPrefix ? `${keyPrefix}.${key}` : key
if (typeof val !== "object") {
return {
...acc,
[nextKey]: val
};
} else {
return {
...acc,
...flatObject(val, nextKey)
};
}
}, {});
console.log(flatObject(obj))
Here is an interative solution using object-scan.
object-scan is a data processing tool, so the main advantage here is that it would be easy to do further processing or processing while extracting the desired information
// const objectScan = require('object-scan');
const myData = { one: 1, two: { three: 3 }, four: { five: 5, six: { seven: 7 }, eight: 8 }, nine: 9 };
const flatten = (data) => {
const entries = objectScan(['**'], {
reverse: false,
rtn: 'entry',
joined: true,
filterFn: ({ isLeaf }) => isLeaf
})(data);
return Object.fromEntries(entries);
};
console.log(flatten(myData));
// => { one: 1, 'two.three': 3, 'four.five': 5, 'four.six.seven': 7, 'four.eight': 8, nine: 9 }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Try this
let x;
try{
x = JSON.parse(prompt("Input your JSON"))
}
catch(e) {
alert("not a valid json input")
}
var res = {};
var constructResultCurry = function(src){ return constructResult(res,src); }
function constructResult(target, src) {
if(!src) return;
target[src.key] = src.val;
}
function buildPath(key, obj, overAllKey) {
overAllKey += (overAllKey ? "." : "") + key;
if(typeof obj[key] != "object") return { key : overAllKey, val : obj[key] };
Object.keys(obj[key]).forEach(function(keyInner) {
constructResultCurry(buildPath(keyInner, obj[key], overAllKey));
});
}
Object.keys(x).forEach(function(k){
constructResultCurry(buildPath(k, x, ""));
});
console.log("**************ALL FIELDS****************")
console.log(res);
console.log("******************************************")
let conf = confirm("do you need a specific field from JSON");
if ( conf )
{
let field = prompt("Input field name")
let results = Object.fromEntries(
Object.entries(res).filter(([key]) => (key.toLowerCase()).includes((field.toLowerCase()))))
prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(results));
console.log(results)
}
else {
prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(res));
}
https://jsfiddle.net/amars404/2n9fprz8/57/