Home > Mobile >  Need help filtering nested json object recursively
Need help filtering nested json object recursively

Time:07-16

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);

  • Related