I have a JSON object "data" in React that has other objects nested inside it, think of it as a directory/ file structure where the number of layers is arbitrary.
const data = {
"item1": {
"item1.1": {
"item1.1.1": {
"item1.1.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "ERROR"
}
}
},
"item1.2": {
"item1.2.1": {
"item1.2.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "WARNING"
}
}
}
},
"item2": {
"item2.1": {
"item2.1.1": {
"item2.1.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "WARNING"
}
},
"item2.1.2": {
"item2.1.2.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "OK"
},
"item2.1.2.2": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "WARNING"
}
}
}
},
"item3": {
"item3.1": {
"item3.1.1": {
"item3.1.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "OK"
}
},
"item3.1.2": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "ERROR"
}
}
}
}
I'm trying to write a function that would filter the object based on the status value.
for example, getStatuses(data, "ERROR") will return a list of all the objects with the status being "ERROR". The expected return value is:
{"item1.1.1.1": {"attr1": [], "attr2": "", "attr3": [], "status" : "ERROR"},
"item3.1.2": {"attr1": [],"attr2": "", "attr3": [], "status" : "ERROR"}
}
My thoughts are that the function needs to loop through the data recursively and then push the matching objects into a list, but my logic seems to be flawed as I am not getting the right results.
const getStatuses= (obj, status) => {
let result = [];
if (obj.status === status) {
result.push(obj)
}
Object.entries(obj).forEach(([, value]) => {
//if object is not a leaf node
if (value !== null && !Array.isArray(value) && typeof value === 'object') {
getStatuses(value, status); //recursive call to dive in another layer
}
});
}
CodePudding user response:
Your function doesn't return anything: the push
happens on a local variable that is lost, and the pushed object is not associated with a key.
I would suggest a recursive generator that returns pairs of keys and associated objects. The caller can then assemble these into an object using Object.fromEntries
:
function* iterMatches(data, status) {
if (Object(data) !== data) return; // A primitive
for (const [key, value] of Object.entries(data)) {
if (value?.status === status) yield [key, value];
yield* iterMatches(value, status);
}
}
const data = {"item1": {"item1.1": {"item1.1.1": {"item1.1.1.1": {"attr1": [],"attr2": "","attr3": [],"status" : "ERROR"}}},"item1.2": {"item1.2.1": {"item1.2.1.1": {"attr1": [],"attr2": "","attr3": [],"status" : "WARNING"}}}},"item2": {"item2.1": {"item2.1.1": {"item2.1.1.1": {"attr1": [],"attr2": "","attr3": [],"status" : "WARNING"}},"item2.1.2": {"item2.1.2.1": {"attr1": [],"attr2": "","attr3": [],"status" : "OK"},"item2.1.2.2": {"attr1": [],"attr2": "","attr3": [],"status" : "WARNING"}}}},"item3": {"item3.1": {"item3.1.1": {"item3.1.1.1": {"attr1": [],"attr2": "","attr3": [],"status" : "OK"}},"item3.1.2": {"attr1": [],"attr2": "","attr3": [],"status" : "ERROR"}}}};
console.log(Object.fromEntries(iterMatches(data, "ERROR")));
CodePudding user response:
By adapting the lovely function iterate
we can easily iterate a tree. On the way we collect all those with the searched status.
This solution is the same as the others. Only easier to find.
const data={item1:{"item1.1":{"item1.1.1":{"item1.1.1.1":{attr1:[],attr2:"",attr3:[],status:"ERROR"}}},"item1.2":{"item1.2.1":{"item1.2.1.1":{attr1:[],attr2:"",attr3:[],status:"WARNING"}}}},item2:{"item2.1":{"item2.1.1":{"item2.1.1.1":{attr1:[],attr2:"",attr3:[],status:"WARNING"}},"item2.1.2":{"item2.1.2.1":{attr1:[],attr2:"",attr3:[],status:"OK"},"item2.1.2.2":{attr1:[],attr2:"",attr3:[],status:"WARNING"}}}},item3:{"item3.1":{"item3.1.1":{"item3.1.1.1":{attr1:[],attr2:"",attr3:[],status:"OK"}},"item3.1.2":{attr1:[],attr2:"",attr3:[],status:"ERROR"}}}};
function getStatuses(data, status) {
var result = {}
const iterate = (obj) => {
if (!obj) {
return;
}
Object.keys(obj).forEach(key => {
var value = obj[key]
if (typeof value === "object" && value !== null) {
iterate(value)
if (value.status == status) {
result[key] = value;
}
}
})
}
iterate(data)
return result;
}
console.log (getStatuses(data,"ERROR"))
CodePudding user response:
You need to save not only the values (the objects with the matching status
) but also the keys, so pushing to an array won't work. You also need a persistent output structure (don't re-create it anew each time the function runs). This can be done with a default argument that gets passed along each recursive call. In each call, loop over both the keys and the values of each child property, and if a value fulfills the condition, assign the key and value to the output.
const data={item1:{"item1.1":{"item1.1.1":{"item1.1.1.1":{attr1:[],attr2:"",attr3:[],status:"ERROR"}}},"item1.2":{"item1.2.1":{"item1.2.1.1":{attr1:[],attr2:"",attr3:[],status:"WARNING"}}}},item2:{"item2.1":{"item2.1.1":{"item2.1.1.1":{attr1:[],attr2:"",attr3:[],status:"WARNING"}},"item2.1.2":{"item2.1.2.1":{attr1:[],attr2:"",attr3:[],status:"OK"},"item2.1.2.2":{attr1:[],attr2:"",attr3:[],status:"WARNING"}}}},item3:{"item3.1":{"item3.1.1":{"item3.1.1.1":{attr1:[],attr2:"",attr3:[],status:"OK"}},"item3.1.2":{attr1:[],attr2:"",attr3:[],status:"ERROR"}}}};
const getStatuses = (inputObj, status, outputObj = {}) => {
for (const [key, value] of Object.entries(inputObj)) {
if (value.status === status) {
outputObj[key] = value;
} else if (typeof value === 'object' && !Array.isArray(value)) {
getStatuses(value, status, outputObj);
}
}
return outputObj;
}
console.log(getStatuses(data, 'ERROR'));
CodePudding user response:
You already got 2 really good answers that followed how you were solving the problem so I just want to add something that it's kinda different.
If in your case the performance isn't a big factor, you could get much more readable and reusable code by separating the problem in 2 parts, which would be (1) flatten the object, and then (2) filtering the objects with the status you want.
You can see an example of what I explained below:
const data = {
"item1": {
"item1.1": {
"item1.1.1": {
"item1.1.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "ERROR"
}
}
},
"item1.2": {
"item1.2.1": {
"item1.2.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "WARNING"
}
}
}
},
"item2": {
"item2.1": {
"item2.1.1": {
"item2.1.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "WARNING"
}
},
"item2.1.2": {
"item2.1.2.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "OK"
},
"item2.1.2.2": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "WARNING"
}
}
}
},
"item3": {
"item3.1": {
"item3.1.1": {
"item3.1.1.1": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "OK"
}
},
"item3.1.2": {
"attr1": [],
"attr2": "",
"attr3": [],
"status" : "ERROR"
}
}
}
};
const makeFlat = (obj, flatObj = {}) => {
Object.entries(obj).forEach(([key, value]) => {
if (!value || typeof value !== 'object')
return;
if (Object.keys(value).includes("status")) {
flatObj[key] = value;
} else {
makeFlat(value, flatObj);
}
});
return flatObj;
}
const filterByStatus = (obj, status) => {
const result = {};
Object.entries(obj).forEach(([key, value]) => {
if(value.status === status) {
result[key] = value;
}
});
return result;
}
const flatObject = makeFlat(data);
console.log(flatObject);
const filteredFlatObject = filterByStatus(flatObject, "ERROR");
console.log(filteredFlatObject);