1

Say I have a json structure that looks like this (directory tree structure) that I want to filter using javascript. I have asked a similar question before, but the requirements have changed and now I need the keep the structure after filtering.

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

    }

Essentially, I want to create a function that lets me remove all the node items (and empty branches) that don't contain a certain status (i.e Warning) and keep the rest within the original structure. This would have to be done recursively because the depth of the nested structure is unknown. For example, filterByStatus(data, "WARNING") would return something like:

{
  "item1": {
    "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.2": {
          "attr1": [],
          "attr2": "",
          "attr3": [],
          "status" : "WARNING"
        }
      }
    }
  }
}
4
  • "and now I need the keep the structure after filtering." Can you be more specific please? You mean not changing the initial object, that is, creating a copy with the desired result, or did I get it wrong? Commented Oct 17, 2022 at 22:57
  • yes, I mean returning a copy of the desired result. Commented Oct 17, 2022 at 23:11
  • Sorry for the delay, the website came back recently and went unnoticed Commented Oct 17, 2022 at 23:50
  • Any questions, please comment so I can edit my answer, I wish you success on your journey :) Commented Oct 17, 2022 at 23:50

2 Answers 2

2

The solution


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 checkRecursively(obj, search, ...st){    
    for(const [key, value] of Object.entries(obj)){        
        let mySt = { found: false };
        
        if(typeof value == 'object')
            obj[key] = iterMatches(value, search, mySt, ...st)
    
        if(!mySt.found)
            delete obj[key]
    }
}

function iterMatches(obj, search, ...st){    
    let result = {...obj}
    
    if(obj?.status == search) {
        st.forEach(item => item.found = true)
        return result;
    }

    checkRecursively(result, search, ...st)
    
    return result
}

console.log(iterMatches(data, 'WARNING'))

The Explanation


1. We iterate over each element of the object, that is, for each keyset of Object.entries(obj) we add a mySet (which is a flag) that determines a path taken in the route.

2. We went deeper using the recursion of the elements, and each level/key traveled we added a new flag

3. When we find an element whose .status is equal to the one we are looking for, we set all the flags that represent the path necessary for this object in question to exist to true

4. When the function is coming back from its recursion, if the first flag is not true, we delete the entire branch below as it is considered not found

Example:


Example

Remarks


If this answer was not satisfactory, or confusing, or did not answer what was asked, please comment so I can edit it.

It's worth mentioning that I'm using google translator to answer, I apologize for any inconvenience

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

1 Comment

Awesome answer, exactly what I was asking for. Thank you and best of luck to you as well!
0

I would build this atop a function which does a deep filter of a nested object for an arbitrary predicate. Then we can easily write filterByStatus by supplying it with a simple predicate testing whether a given object has the given status property. Here's one version:

const deepFilter = (pred) => (o) =>
  (pred (o) || Object (o) !== o) ? o : Object .fromEntries (Object .entries (o) .flatMap (
    ([k, v, r = deepFilter (pred) (v)]) => 
      Object (r) === r  && Object .keys (r) .length > 0 ? [[k, r]] : []
  ))

const filterByStatus = (target) => deepFilter (({status}) => status == target)

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

If you like, you might extract out isObject and isNonEmptyObject helper functions and rewrite it like this:

const deepFilter = (pred) => (o) =>
  (pred (o) || !isObject (o)) ? o : Object .fromEntries (Object .entries (o) .flatMap (
    ([k, v, r = deepFilter (pred) (v)]) => isNonEmptyObject (r) ? [[k, r]] : []
  ))

I like this API where we supply the target status and get back a reusable function we can apply to our data. But if you prefer the style in the question, it's simple enough to write

const filterByStatus = (data, targetStatus) => 
  deepFilter (({status}) => status == targetStatus) (data)

filterByStatus (data, 'WARNING')

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.