Reputation: 27
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"
}
}
}
}
}
Upvotes: 1
Views: 593
Reputation: 50797
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')
Upvotes: 0
Reputation: 981
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'))
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
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
Upvotes: 2