0

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
            }
        });
    }
1
  • "I am not getting the right results": can you be more specific? Commented Jul 15, 2022 at 19:14

5 Answers 5

2

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

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

1

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

Comments

1

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

Comments

1

I keep handy various useful functions. One of them is pathEntries, which works something like Object .entries, but instead of returning just the root values, keeps the entire paths to all nodes. For instance, one of them might be

  [["item2", "item2.1", "item2.1.1", "item2.1.1.1", "status"], "WARNING"]

and another one:

  [["item2", "item2.1", "item2.1.1"], {
     "item2.1.1.1": {attr1: [], attr2: "", attr3: [], status: "WARNING"}
  }]

Using this, we can easily get all of these entries, filter to include only those with the proper status node, then trim the paths to include only the last node, then rehydrate an object with these values

const deepObjFilter = (pred) => (o) => Object .fromEntries (
  pathEntries (o) .filter (([p, v]) => pred (v)) .map (([p, v]) => [p .at (-1), v])
)

const pathEntries = (o, p = []) => [
  ... (p .length > 0 ? [[p, o]] : []),
  ... (Object (o) === o ? Object .entries (o) .flatMap (
        ([k, v]) => pathEntries (v, [...p, Array .isArray (o) ? Number (k) : k])) : []
      )
]

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 (deepObjFilter (o => o .status === 'ERROR') (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

And of course we can also preconfigure the search like this:

const filterByStatus = (status) => deepObjFilter (o => o .status === status)
// later
filterByStatus ('ERROR') (data)

or even

const getErrors = filterByStatus ('ERROR')
// later
getErrors (data)

The important point here is that having useful utility functions around can make such problems quite simple.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.