I have an array of objects with items (only have name property) and groups (with a children property, they may contain items or other groups) and I need to get a full path to needle value, so in this case it'd be myObj[2]["children"][0]["children"][1]["children"][0], plus I'm limited to quite old JS version ECMA 262 (I'm using it inside Photoshop)
var myObj = [
{
"name": "group1",
"children": [
{
"name": "group2",
"children": [
{
"name": "item0"
}]
}]
},
{
"name": "item1"
},
{
"name": "needleGroup",
"children": [
{
"name": "needleNestedGroup",
"children": [
{
"name": "item3"
},
{
"name": "needleNestedDeeperGroup",
"children": [
{
"name": "needle"
}]
}]
}]
}];
My first idea was to transform object to array or arrays so it'd be easier to process, so my object became
[
[
[
"item0"
]
],
"item1",
[
[
"item3",
[
"needle"
]
]
]
];
But.. that's it. I can't figure out hot to track down only the indexes I need. Could you please point out a correct direction.
Use a recursive function to look for the item you want. Once the function find it, it will return an array. Each step back of the recursion will unshift the object key of this step:
function find(obj, item) {
for(var key in obj) { // for each key in obj (obj is either an object or an array)
if(obj[key] && typeof obj[key] === "object") { // if the current property (value of obj[key]) is also an object/array
var result = find(obj[key], item); // try finding item in that object
if(result) { // if we find it
result.unshift(key); // we shift the current key to the path array (result will be an array of keys)
return result; // and we return it to our caller
}
} else if(obj[key] === item) { // otherwise (if obj[key] is not an object or array) we check if it is the item we're looking for
return [key]; // if it is then we return the path array (this is where the path array get constructed)
}
}
}
The output of this function will be an array of keys leading to item. You can easily transform it to the format in the question:
function findFormatted(obj, item) {
var path = find(obj, item); // first let's get the path array to item (if it exists)
if(path == null) { // if it doesn't exist
return ""; // return something to signal its inexistance
}
return 'myObj["' + path.join('"]["') + '"]'; // otherwise format the path array into a string and return it
}
Example:
function find(obj, item) {
for(var key in obj) {
if(obj[key] && typeof obj[key] === "object") {
var result = find(obj[key], item);
if(result) {
result.unshift(key);
return result;
}
} else if(obj[key] === item) {
return [key];
}
}
}
function findFormatted(obj, item) {
var path = find(obj, item);
if(path == null) {
return "";
}
return 'myObj["' + path.join('"]["') + '"]';
}
var myObj = [{"name":"group1","children":[{"name":"group2","children":[{"name":"item0"}]}]},{"name":"item1"},{"name":"needleGroup","children":[{"name":"needleNestedGroup","children":[{"name":"item3"},{"name":"needleNestedDeeperGroup","children":[{"name":"needle"}]}]}]}];
console.log("find(myObj, \"needle\"): " + JSON.stringify(find(myObj, "needle")));
console.log("findFormatted(myObj, \"needle\"): " + findFormatted(myObj, "needle"));
Note: The indexes for the arrays are also formatted as strings, but that won't be a problem as someArray["2"] is equivalent to someArray[2].
I've created something you might use. The code below returns an Array of paths to keys, values, objects you are looking for.
See snippet and example to see what you can do.
To make it work you have to pass key and/or value you want to find in element and element which is an Array or Object.
It's written in newer JS standard but it shouldn't be a problem to compile it to older standard.
function findKeyValuePairsPath(keyToFind, valueToFind, element) {
if ((keyToFind === undefined || keyToFind === null) &&
(valueToFind === undefined || valueToFind === null)) {
console.error('You have to pass key and/or value to find in element!');
return [];
}
const parsedElement = JSON.parse(JSON.stringify(element));
const paths = [];
if (this.isObject(parsedElement) || this.isArray(parsedElement)) {
checkObjOrArr(parsedElement, keyToFind, valueToFind, 'baseElement', paths);
} else {
console.error('Element must be an Object or Array type!', parsedElement);
}
console.warn('Main element', parsedElement);
return paths;
}
function isObject(elem) {
return elem && typeof elem === 'object' && elem.constructor === Object;
}
function isArray(elem) {
return Array.isArray(elem);
}
function checkObj(obj, keyToFind, valueToFind, path, paths) {
Object.entries(obj).forEach(([key, value]) => {
if (!keyToFind && valueToFind === value) {
// we are looking for value only
paths.push(`${path}.${key}`);
} else if (!valueToFind && keyToFind === key) {
// we are looking for key only
paths.push(path);
} else if (key === keyToFind && value === valueToFind) {
// we ale looking for key: value pair
paths.push(path);
}
checkObjOrArr(value, keyToFind, valueToFind, `${path}.${key}`, paths);
});
}
function checkArr(array, keyToFind, valueToFind, path, paths) {
array.forEach((elem, i) => {
if (!keyToFind && valueToFind === elem) {
// we are looking for value only
paths.push(`${path}[${i}]`);
}
checkObjOrArr(elem, keyToFind, valueToFind, `${path}[${i}]`, paths);
});
}
function checkObjOrArr(elem, keyToFind, valueToFind, path, paths) {
if (this.isObject(elem)) {
checkObj(elem, keyToFind, valueToFind, path, paths);
} else if (this.isArray(elem)) {
checkArr(elem, keyToFind, valueToFind, path, paths);
}
}
const example = [
{
exampleArr: ['lol', 'test', 'rotfl', 'yolo'],
key: 'lol',
},
{
exampleArr: [],
key: 'lol',
},
{
anotherKey: {
nestedKey: {
anotherArr: ['yolo'],
},
anotherNestedKey: 'yolo',
},
emptyKey: null,
key: 'yolo',
},
];
console.log(findKeyValuePairsPath('key', 'lol', example)); // ["baseElement[0]", "baseElement[1]"]
console.log(findKeyValuePairsPath(null, 'yolo', example)); // ["baseElement[0].exampleArr[3]", "baseElement[2].anotherKey.nestedKey.anotherArr[0]", "baseElement[2].anotherKey.anotherNestedKey", "baseElement[2].key"]
console.log(findKeyValuePairsPath('anotherNestedKey', null, example)); //["baseElement[2].anotherKey"]
I came accross this issue and took the chance to create find-object-paths, which solves this problem: Finding paths in an object by either keys, values or key/value combinations.
NPM: find-object-paths
Github: getPaths
Example object:
{
"company": {
"name": "ACME INC",
"address": "1st Street, Toontown, TT",
"founded": "December 31st 1969",
"hasStocks": true,
"numberOfEmployees": 2,
"numberOfActors": 3
},
"employees": [
{
"employeeNumber": 1,
"name": "Hugo Boss",
"age": 65,
"isCEO": true
},
{
"employeeNumber": 2,
"name": "Herbert Assistant",
"age": 32,
"isCEO": false
}
],
"actors": [
{
"actorId": 1,
"name": "Bugs Bunny",
"retired": false,
"playedIn": [
{
"movie": "Who Framed Roger Rabbit",
"started": 1988
},
{
"movie": "Space Jam",
"started": 1996
},
{
"movie": "Looney Tunes: Back in Action",
"started": 2003
}
]
},
{
"actorId": 2,
"name": "Pepé le Pew",
"retired": true,
"playedIn": [
{
"movie": "For Scent-imental Reasons",
"started": 1949
}
]
},
{
"actorId": 3,
"name": "Marvin the Martian",
"retired": true,
"playedIn": [
{
"movie": "Marvin the Martian in the Third Dimension",
"started": 1996
},
{
"movie": "Duck Dodgers and the Return of the 24½th Century",
"started": 1980
},
{
"movie": "Hare-Way to the Stars",
"started": 1958
}
]
},
{
"actorId": 4,
"name": "Yosemite Sam",
"retired": false,
"playedIn": [
{
"movie": "Who Framed Roger Rabbit",
"started": 1988
},
{
"movie": "Space Jam",
"started": 1996
},
{
"movie": "Looney Tunes: Back in Action",
"started": 2003
}
]
}
],
"distributionChannels": [
"Celluloyd",
[
"VHS",
"Betamax",
"DVD",
"Blueray"
],
[
"channel",
12,
true
]
]
}
So, the basic usage could be:
import { findObjectPaths } from 'findObjectPaths';
class TestMe {
static async main() {
let acmeInc = {};
rawFileContent = fs.readFileSync(p.resolve(__dirname, 'acmeInc.json'), 'utf-8');
acmeInc = JSON.parse(rawFileContent);
let path = findObjectPaths(acmeInc, {key: 'founded'});
// company.founded
path = findObjectPaths(acmeInc, {value: 'December 31st 1969'});
// company.founded
const allPaths: string[] = findObjectPaths(acmeInc, {key: 'actorId'}) as string[];
/* [ 'actors[0].actorId',
'actors[1].actorId',
'actors[2].actorId',
'actors[3].actorId' ]
*/
const ceoPath = findObjectPaths(acmeInc, {key: 'isCEO', value: true});
// employees[0].isCEO
}
}
TestMe.main();
See the full documentation here: https://github.com/maugenst/getPaths#readme
BR
Assuming that you have a nested and repetitive pattern of objects in your data-set, the following solution would do the trick for you.
const nodePath = { value: [] };
function findTreeNodeAndPath(
targetNodeKey,
targetNodeValue,
nodeChildrenKey,
tree
) {
if (tree[targetNodeKey] === targetNodeValue) {
nodePath.value.push(tree);
return;
}
if (tree[nodeChildrenKey].length > 0) {
tree[nodeChildrenKey].forEach(children => {
if (nodePath.value.length < 1) {
findTreeNodeAndPath(
targetNodeKey,
targetNodeValue,
nodeChildrenKey,
children
);
}
});
} else if (tree[nodeChildrenKey].length === 0) {
return;
}
if (nodePath.value.length > 0) {
nodePath.value.push(tree);
}
}
const exampleData = {
name: "Root",
children: [
{
name: "A2",
children: [
{
name: "AC1",
children: [
{
name: "ACE1",
children: []
}
]
},
{
name: "AC2",
children: [
{
name: "ACF1",
children: []
},
{
name: "ACF2",
children: [
{
name: "ACFG1",
children: []
}
]
},
{
name: "ACF3",
children: [
{
name: "ACFH1",
children: []
}
]
}
]
}
]
}
]
};
findTreeNodeAndPath("name", "ACFG1", "children", exampleData);
console.log(nodePath.value)
The recursive part of the code will iterate on the children of the current node. The existing strategy here is depending on the nodePath.value having at least one element, which indicates that it found the targetted node. Later on, it'll skip the remaining nodes and would break out of recursion.
The nodePath.value variable will give you the node-to-root path.
I need to go through a list of objects to find the element and add a new element to the root, I can scroll through the list and find the element, but I can not add to the correct level
var data = [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"children": [
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5
}
];
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
a.push(element); // ERROR
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
var element = {
"children": [{"id": 6}]
};
findById(data, 5, element);
document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');
https://jsfiddle.net/4nrsccnu/
Use Object.assign to merge the properties from the element object to the current object of the iteration.
var data = [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"children": [
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5
}
];
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
Object.assign(a, element);
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
var element = {
"children": [{"id": 6}]
};
findById(data, 5, element);
console.log(data);
You cannot push, because a is an object. However, you can simply add the desired property (in your case, children), and then assign it a value (in your case, the element).
var data = [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"children": [
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5
}
];
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
a.children = element; // Add property 'children'
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
// remove property name from element, as that is being added in the function
var element = [
{"id": 6}
];
findById(data, 5, element);
document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');
The push function is used for arrays, not to object. Check for details https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push.
If you want to add a single keypair to your object, you can use Object.keys and Object.values. Like this:
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
var key = Object.keys(element)[0];
var value = Object.values(element)[0];
a[key] = value;
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result;
}
I'm receiving the following data in my JS from a WebService :
{
"fire": {
"totalOccurence": 2,
"statsByCustomer": [
{
"idCustomer": 1,
"occurence": 1
},
{
"idCustomer": 2,
"occurence": 1
}
]
},
"flood": {
"totalOccurence": 1,
"statsByCustomer": [
{
"idCustomer": 1,
"occurence": 1
}
]
}
}
What's the fastest way to create the following object as a result :
{
"1": {
"fire": 1,
"flood": 1
},
"2": {
"fire": 1,
"flood": 0
}
}
I'm actually doing multiple forEach to format the data myself, but i think it's pretty ugly and not efficient..
PS : the key for the result map is the customer Id
Any idea on how to do this the right way?
Thanks for your help !
You could iterate the outer object's keys and then the inner arrays. If an result object does not exist, create one with the wanted keys and zero values.
var data = { fire: { totalOccurence: 2, statsByCustomer: [{ idCustomer: 1, occurence: 1 }, { idCustomer: 2, occurence: 1 }] }, flood: { totalOccurence: 1, statsByCustomer: [{ idCustomer: 1, occurence: 1 }] } },
result = {},
keys = Object.keys(data);
keys.forEach(function (k) {
data[k].statsByCustomer.forEach(function (a) {
if (!result[a.idCustomer]) {
result[a.idCustomer] = {};
keys.forEach(function (kk) {
result[a.idCustomer][kk] = 0;
});
}
result[a.idCustomer][k] += a.occurence;
});
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
inputJson = {
"mn": {
"mt1": 1,
"mtop": 2,
"ot1": 3
},
"ln": {
"mt1": 4,
"mtop": 5,
"ot1": 6
}
}
OutputArrayOfJson=[
{ rs: "mt1", mn: 1, ln: 4 },
{ rs: "mtop", mn: 2, ln: 5 },
{ rs: "ot1", mn: 3, ln: 6 }
]
rs is hardcode Key.
I don't know why am having hard time doing this operation.
It is a conversion of javascript objects
inputJson = {
"mn": {
"mt1": 1,
"mtop": 2,
"ot1": 3
},
"ln": {
"mt1": 4,
"mtop": 5,
"ot1": 6
}
}
d = {};
for(var key1 in inputJson){
for(var key2 in inputJson[key1]) {
if(!(key2 in d)){
d[key2]={};
}
d[key2][key1] = inputJson[key1][key2];
}
}
v = [];
for(var k in d){
var o = {};
o.rs=k;
for(var k2 in d[k]){
o[k2] = d[k][k2];
}
v.push(o);
}
//result is in v
note: the next time you should shown example code or not will help
You can iterate through the object and push each root property inside an array:
var arr = [];
for (var p in inputJson){
arr.push(inputJson[p]);
}
console.log(arr);
Let's say I have this data, it is the result from a query.
[
[
{
"brgy_locat": "Kauswagan",
"countlow": 8
},
{
"brgy_locat": "Katugasan",
"countlow": 24
},
{
"brgy_locat": "Comagascas",
"countlow": 3
},
{
"counthigh": 7,
"brgy_locat": "Barangay 9"
},
[
{
"brgy_locat": "Barangay 11",
"countmedium": 1
}
],
[],
[],
[],
[
{
"brgy_locat": "Mabini",
"countmedium": 1
}
],
[
{
"counthigh": 27,
"brgy_locat": "Barangay 6"
},
{
"counthigh": 3,
"brgy_locat": "Mabini"
},
{
"counthigh": 2,
"brgy_locat": "Barangay 2"
},
{
"counthigh": 7,
"brgy_locat": "Barangay 9"
},
{
"counthigh": 17,
"brgy_locat": "Comagascas"
},
{
"counthigh": 1,
"brgy_locat": "Tolosa"
},
{
"counthigh": 33,
"brgy_locat": "Barangay 7"
}
]
]
]
I wanted it to be group by brgy_locat and sum all the values from countlow, countmedium and counthigh if brgy_locat is the same.
Somehow like this:
[
{
"brgy_locat": "Kauswagan",
"countlow": 8,
"countmedium": 1,
"counthigh": 5
}
]
Values above are just samples. Take a look at this JSFiddle I made.
I saw your fiddle, and you should add a flatten function, and make your sum function transform the current to 0 when it is undefined
You can do a function like that:
function sum(numbers) {
return _.reduce(numbers, function (result, current) {
return result + parseFloat(current || 0);
}, 0);
}
function summarize(data) {
var summary = _(data).chain()
.flatten()
.groupBy("brgy_locat")
.map(function (value, key) {
return {
brgy: key,
low: sum(_(value).chain().pluck("countlow").value()),
medium: sum(_(value).chain().pluck("countmedium").value()),
high: sum(_(value).chain().pluck("counthigh").value())
}
})
.value();
return summary;
}
When you call it passing the data you gave as example, the result will be what you asked for...
I misunderstood the question the first time around. You still want to flatten, and you can still use groupby. I found that using _.each and the index argument is very useful in this. This should do it for you:
var temp = _.chain(data)
.flatten()
.groupBy('brgy_locat')
.each(function(eachObj,index) {
if (!result.findWhere({ 'brgy_locat': index })) {
var newObj = {
'brgy_locat': index,
countlow: _.reduce(eachObj, function(memo, obj) {
if (!obj.countlow) return memo;
return memo + obj.countlow;
},0),
countmedium: _.reduce(eachObj, function(memo, obj) {
if (!obj.countmedium) return memo;
return memo + obj.countmedium;
},0),
counthigh: _.reduce(eachObj, function(memo, obj) {
if (!obj.counthigh) return memo;
return memo + obj.counthigh;
},0)
};
result.push(newObj);
}
});