In search of assistance with recursively filtering a nested JSON object

I have a JSON object called "data" in React that contains nested objects, resembling a hierarchical directory or file structure with an arbitrary number of layers.

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 attempting to create a function that filters the object based on the status value.

For instance, calling getStatuses(data, "ERROR") should return a list of all objects with the status set to "ERROR". The expected result would be:

{"item1.1.1.1": {"attr1": [], "attr2": "", "attr3": [], "status" : "ERROR"},
 "item3.1.2": {"attr1": [], "attr2": "", "attr3": [], "status" : "ERROR"}
}

My approach involves recursively looping through the data and adding matching objects to a list, but I seem to encounter issues as the results aren't as expected.

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 into another layer
        }
    });
}

Answer №1

Your function lacks a return statement: the push occurs on a local variable that is not retained, resulting in the pushed object losing its association with a key.

To address this, I recommend implementing a recursive generator that yields pairs of keys and their associated objects. The consumer can then construct an object by using Object.fromEntries:

function* iterMatches(data, status) {
    if (Object(data) !== data) return; // Handles primitive values
    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")));

Answer №2

When we modify the iterate function, we can effortlessly traverse a tree while gathering those items with the desired status.

This approach is identical to the rest but stands out for its ease of identification.

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

Answer №3

Ensure to retain both the values (objects with matching status) and their respective keys. Adding them to an array will not suffice. It's crucial to maintain a consistent output structure that persists across recursive function calls, instead of recreating it every time. Achieve this by using a default argument passed through each iteration. Within each step, iterate over the keys and values of every child property and if a value meets the specified condition, set both the key and value in 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'));

Answer №4

You've already received two insightful answers that align with your problem-solving approach, so I'd like to offer a slightly different perspective.

If performance isn't critical in your scenario, you can achieve more readable and reusable code by breaking down the problem into two parts: first flattening the object, and then filtering the objects based on the desired status.

Below is an example illustrating this concept:

const data = {
    // Object structure here
};

const makeFlat = (obj, flatObj = {}) => {
    // Code for flattening the object
}

const filterByStatus = (obj, status) => {
    // Code for filtering by status
}

// Flatting the object
const flatObject = makeFlat(data);
console.log(flatObject);

// Filtering the flat object by 'ERROR' status
const filteredFlatObject = filterByStatus(flatObject, "ERROR");
console.log(filteredFlatObject);

Answer №5

I have in my repertoire a variety of helpful functions at my disposal. One that stands out is pathEntries, which operates similarly to Object.entries, but instead of just returning the root values, it retains the complete paths to all nodes. As an example:

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

By utilizing this function, we can easily extract these entries, filter them to include only those with the correct status node, then shorten the paths to contain only the final node, and reconstruct 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"}, "...

console .log (deepObjFilter (o => o .status === 'ERROR') (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

Furthermore, we can also customize the search like so:

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

or even

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

The crux here is that having handy utility functions available can simplify complex problems such as these.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to refresh the main frame

Here is an example of parent code: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Parent</title> </head> <body> <iframe src="https://dl.dropboxusercontent.com/u/4017788/Labs/child.html" width ...

What is the correct way to use a Higher Order Component to wrap the child components of a parent and display them in React?

Starting with a simple example that can be easily solved using React.cloneElement, but wanting more freedom and complexity in the project, I am looking for an alternative solution. The goal is to enhance the children of a Parent component with additional ...

Modify the web address on a WordPress site

My template contains various titles, all pulled from the database. When I select a specific title, it opens up a second page with a detailed description and a URL link: /category-details/?expertise_category=INSTITUTIONAL However, I would prefer the link ...

Building interactive web forms with real-time validation using CodeIgniter

I'm looking for a way to use ajax (jquery library) to validate my forms. Specifically, I have a form that requires a minimum password length of 6 characters. Currently, I have implemented the validation rule as follows, $this->form_validation-> ...

Update the button label using data from a JSON source

While I am iterating through my JSON data, I want to dynamically set the button title based on the JSON property title. How can I achieve this? func getDataFromJSON(){ //Calling getCollectionViewData from the RestParser class RestParser.sharedInst ...

CSV files often have JSON fields surrounded by pairs of quotes

For my latest project, I am working on a Dictionary<string, TimeSpan>. To convert this dictionary to JSON, I use JsonConvert.SerializeObject("dictionary"). The output is displayed in the image linked below: https://i.sstatic.net/ihYYw.png ...

The specified converter for "enum" is not inheriting from JsonConverter or does not contain a public parameterless constructor (Swagger | Enum)

I'm currently working on integrating swagger into our JSON API. Following recommendations from the JsonApiDotNetCore team, I've added the necessary packages. As I include swagger hooks in Program.cs to generate the openapi document, I encounter ...

Display a notification upon successfully submitting data in Laravel and Vue.js

Hello there; I am new to Laravel and Vue.js. I have encountered an issue when submitting a form with validation problems. The error message for a required input field, such as the "Brand Name" field, appears correctly, but after successfully submitting the ...

Waiting for jQuery until the data has been fetched

In my code, there is a JavaScript function that fetches data from a server using an AJAX POST request. function fetchData(keyword) { $.post( 'http://example.com/apiv5/ajax/', { command: 'fetchxxx', ...

Transforming HTML-encoded JSON into JSON format with BeautifulSoup

Seeking a solution with parsing raw HTML from the bandsintown website using beautifulSoup to access a JSON embedded in a script, particularly the "eventsJsonLd" key in the script section of the page source: "jsonLdContainer":{"eventsJsonLd":[{"@context":" ...

Is there a way to align the Material UI mobile menu to the left edge of the screen?

Need help with this UI issue: Currently working on designing a mobile web app using React and Material UI. I'm struggling to get the sidebar menu to start from the left edge of the screen, as it seems to have an unwanted margin preventing it. Any sug ...

Easily toggle between various image source paths

In my current application, all HTML files have hardcoded relative image paths that point to a specific directory. For example: <img src="projectA/style/images/Preferences.png"/> Now, I am considering switching between two different project modes: & ...

Exploring Paths in a JSON Structure using Scala and a List

When working with Play Scala, we can navigate through properties in an object using the \ method: val name = (json \ "user" \ "name") If our path is defined in a list, how can we navigate to the node? val path = List("user","name") val n ...

I am feeling perplexed by the alert!

function updateDateFormat(){ // alert("hi"); $(".tour-dates ul li").each(function(){ // alert(monthConverter($(this).find(".month").text())); var replace = monthConverter($(this).find(".month").text()); $(this).find(".month").tex ...

What is the process for transforming a JSON input from a Restlet request into a Plain Old Java Object (POJO)?

In the quest to create a simple method that can manipulate JSON data representing a plain old Java object (POJO), a few challenges have emerged. The example provided showcases an attempt to receive and return a POJO using Jackson Representation: @Put("jso ...

Assistance with themed implementation for single-page web applications in JavaScript

My goal is to incorporate theme support into my single page application. The catch is that the theme change needs to be done locally through JavaScript without making any server calls, in order to work in offline mode. Since I am using angularjs, the HTML ...

Do individual JavaScript script tags operate independently of one another in terms of error handling?

My main goal is to establish a connection to my server using websockets and send messages to the browser for remote page reloads. I want to ensure that this code runs independently of any other errors on the page, allowing me to remotely refresh the page i ...

Trading keys between arrays in JavaScript

I have a set of simple objects contained within an array: var myObject1 = [ {name: "value1", adddress: "545454545445"}, {name: "value2", adddress: "323233223"}, {name: "value3", adddress: "332 ...

js issue with passing form data to use with PHP mail function

As a novice, I am diving into the world of HTML webpage creation. Utilizing a free online template, my current project involves developing a Contact Page that triggers a php script to send an email with the captured fields. While I've successfully man ...

Mapping through an array prop in React is not supported

Upon fetching an API in componentDidMount(), I store the JSON data in a state and pass that state as a prop. The issue arises when trying to display data from arrays. <Component details={this.state.details} /> JSON data example: { "adult" ...