Converting nested objects with different lengths into an array of objects

Hey everyone, I've encountered an interesting problem that I managed to solve. However, my solution is not elegant at all. I'm curious to see what creative solutions others can come up with :)

The challenge involves converting the provided response:

const response = {
        "device": {
            "name": "Foo",
            "type": "Bar",
            "telemetry": [
                {
                    "timeStamp": "2022-06-01T00:00:00.000Z",
                    "temperature": 100,
                    "pressure": 50
                },
                ...
            ]
        }
};

Using these selection criteria:

const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature']

The goal is to generate results like this:

[
  {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-01T00:00:00.000Z", "device/telemetry/temperature": 100},
  ...,
]

If you're interested, I'd love to hear your approach! Below is my somewhat messy code - still getting the hang of TypeScript, so bear with me :D

EDIT #1 Clarification: The response format can vary, including deeper levels of nesting or additional arrays like "superTelemetry". However, the selection criteria will only work with one array, never both.

Although the structure may change, we can rely on this rule to guide our implementation. Feel free to share any insights!

function createRecord(key: string, value: any){
  return new Map<string, any>([[key, value]])
}

function getNestedData (data: any, fieldPath: string, records: Map<string, any[]>=new Map<string, any[]>()) {
    let dataPoints: any = [];
    const paths = fieldPath.split('/')
    ...
  }

Answer №1

It appears that you are seeking a solution to generate the desired output based on the provided information. If so, one approach is to utilize Array.map() in conjunction with the Array.forEach() method.

Here is an example for you to try:

const response = {
  "device": {
    "name": "Foo",
    "type": "Bar",
    "telemetry": [
      {
        "timeStamp": "2022-06-01T00:00:00.000Z",
        "temperature": 100,
        "pressure": 50
      },
      {
        "timeStamp": "2022-06-02T00:00:00.000Z",
        "temperature": 100,
        "pressure": 50
      },
      {
        "timeStamp": "2022-06-03T00:00:00.000Z",
        "temperature": 100,
        "pressure": 50
      },
      {
        "timeStamp": "2022-06-04T00:00:00.000Z",
        "temperature": 100,
        "pressure": 50
      },
      {
        "timeStamp": "2022-06-05T00:00:00.000Z",
        "temperature": 100,
        "pressure": 50
      }
    ]
  }
};

const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature'];

const res = response.device.telemetry.map(obj => {
  const o = {};
  fields.forEach(item => {
    const splittedItem = item.split('/');
    o[item] = (splittedItem.length === 2) ? response[splittedItem[0]][splittedItem[1]] : obj[splittedItem[2]];
  });
  return o;
})

console.log(res);

Answer №2

My main focus will be on the implementation and runtime behavior, rather than the types involved. I have intentionally used loose typings like any and string instead of more specific generic object types. Here's a breakdown of the logic:

function getNestedFields(data: any, paths: string[]): any[] {

If the input data is an array, we need to apply the getNestedFields() function recursively to each element of the array and then combine all the results into a single array. This involves checking for an array and making a recursive call:

  if (Array.isArray(data)) return data.flatMap(v => getNestedFields(v, paths));

When data is not an array, our goal is to gather the required information. For example, with paths like

['foo/bar', 'foo/baz/qux', 'x/y', 'x/z']
, we make recursive calls to
getNestedFields(data.foo, ["bar", "baz/qux"])
and
getNestedFields(data.x, ["y", "z"])
. To achieve this, we split each path element at the first slash "/" and store the results in a new object structure where keys are before the slash and values are after it.

There are some edge cases to consider here. For paths without slashes, resulting in an empty value such as ["foo"], we handle them by calling

getNestedFields(data.foo, [""])
. Additionally, when a path is just an empty string "", indicating the base case for data itself, we handle it by directly returning [{"": data}] instead of another recursive call.

The structure looks something like this:

  const pathMappings: Record<string, string[]> = {};
  let emptyPathInList = false;
  paths.forEach(path => {
    if (!path) {
      emptyPathInList = true;
    } else {
      let slashIdx = path.indexOf("/");
      if (slashIdx < 0) slashIdx = path.length;
      const key = path.substring(0, slashIdx);
      const restOfPath = path.substring(slashIdx + 1);
      if (!(key in pathMappings)) pathMappings[key] = [];
      pathMappings[key].push(restOfPath);
    }
  })

For each entry in pathMappings (with keys and corresponding values), we perform recursive calls to getNestedFields(). The nested fields obtained have relative keys to data[key], which require modification by adding key and a slash as prefixes. Handling special cases like empty paths or nullish data to prevent runtime errors adds complexity to this step.

  const subentries = Object.entries(pathMappings).map(([key, restsOfPath]) =>
    (data == null) ? [{}] : // <-- Handle nullish data
      getNestedFields(data[key], restsOfPath)
        .map(nestedFields =>
          Object.fromEntries(Object.entries(nestedFields)
            .map(([path, value]) =>
              [key + (path ? "/" : "") + path, value])))
  )

After obtaining all sub-entries with modified keys, we may include an additional entry corresponding to data in cases where emptyPathInList is true. Finally, combining these sub-entries using the Cartesian product method allows us to create a unified object structure for each entry.

  return subentries.reduce((a, v) => v.flatMap(vi => a.map(ai => ({ ...ai, ...vi }))), [{}])
}

Testing the functionality reveals accurate results for both basic and complex scenarios, showcasing support even for different arrays being traversed within the function.

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

Enhance autocomplete suggestions by including supplementary information

A unique feature on my website is a field that provides autocomplete options for person names, such as "Obama, Barack" or "Lincoln, Abe." These individuals also come with additional attributes, like "Birthplace" and "Phone number." I want the selected opt ...

What is the best way to change JSON into a string format?

Similar Question: Converting JavaScript Object to JSON String I am dealing with a JSON object in JavaScript and I need to change it into a string. Is there a specific function I should use for this conversion? Appreciate any assistance, ...

Selecting and Uploading Multiple Files in Internet Explorer (less than version 9)

I have been struggling with selecting and uploading multiple files at once to the server. Here is the code I am currently using: <html> <body> <form action="upload.php" method="post" enctype="multipart/form-data" name="myForm"> <l ...

How to animate the needle image in a speedometer using jQuery

My project involves a background image of a speedometer. I am working on positioning the needle accurately without animation. The position of the needle in the half circle must reflect the value (speed) it represents. Instead of answers, I am seeking rec ...

Using AJAX to upload an image and passing multiple parameters

I'm facing an issue when trying to upload an image along with other input text in a form and send it to ajax_php_file.php. Whenever I upload the image, all my input text fields appear empty. Any assistance would be greatly appreciated. Thank you very ...

Embarking on the GSAP journey

I'm attempting my first animation using GSAP, but no matter what I try, nothing seems to be working. I've even tried using example code without success. Within my PHP file, I have the following code snippet: <head> <script src="https:/ ...

Issue 2339: Dealing with || and Union Types

I've encountered an interesting issue while working with TypeScript and JavaScript. I created a code snippet that runs perfectly in JavaScript but throws a syntax error in TypeScript. You can check it out in action at this TypeScript Sandbox. Essenti ...

Utilizing *ngIf for Showing Elements Once Data is Completely Loaded

While working on my Angular 2 app, I encountered an issue with the pagination UI loading before the data arrives. This causes a visual glitch where the pagination components initially appear at the top of the page and then shift to the bottom once the data ...

Tips for targeting a specific div element within a webview using code in Android app development

During my exploration of focusing on a webview in Android development, I encountered a question regarding setting focus on a div element. Despite making the div element editable, neither webView.requestFocus() nor document.getElementById('container&ap ...

Developing a Node.js API using Express and MySQL involves utilizing the WHERE IN clause and binding parameters with multiple comma-separated values

Having a URL structure as shown below, where multiple comma-separated values can be added to the URL: localhost:4001/api/v1/users/search?title=mr,dr This is my query implementation: router.get('/search?', function(req, res, next) { var ...

Favicon glitch occurred following the inclusion of Google Adsense

Is there anyone out there who can help me with this issue? I recently entered into a partnership with Google and, as part of the process, had to insert the Google AdSense code onto my website in order to be verified and show ads. Prior to this partnership ...

Tips on implementing href within HTML span tags while utilizing TSX syntax

Having recently started with Typescript and React, I am encountering an issue that I hope someone can help me with. In my code, there is a line that used to work perfectly fine when it was written in Javascript/React: <p><span href="#" id="nsTool ...

Insert a new key/value pair into a designated object in Javascript

var data = { "2738": { "Question": "How are you?", "Answer": "I'm fine" }, "4293": { "Question": "What's your name?", "Answer": "My name is John" } } var newQuestion = "Where do you live?"; var newAnswer = "I liv ...

Utilizing Vue3: A guide to constructing an API URL and retrieving data upon route modification

I am currently working on a project where I need to display the borders of a country using the restcountries API. To achieve this, I want to create clickable buttons that will navigate the user to specific border countries. Here is snippet of my code wher ...

Cypress has the ability to exclude certain elements from its testing

How do I locate a clickable element using the cypress tool? The clickable element always contains the text "Login" and is nested inside the container div. The challenge lies in not knowing whether it's an <button>, <a>, or <input type=& ...

Expanding material UI theme choices through module augmentation is currently ineffective with TypeText

For those experiencing the issue, a codesandbox has been provided for convenience. Click here to access the codesandbox. Curiously, the TypeText feature is not functioning properly while the SimplePaletteColorOptions is working as expected. Despite being ...

The Intellisense feature does not function properly when attempting to import the module from a Content Delivery Network in Visual Studio Code

My process of importing three.js looked like this: import { WebGLRenderer } from 'three'; Initially, the autocomplete feature was working perfectly fine (displayed in image 1). However, when I tried to import from a CDN using the following synt ...

Troubleshooting Firebase: How come my code is only accessing the initial document in my Collection?

After spending hours searching, I still can't find a solution to my problem with this code. The Firebase log file only shows: "after for each =undefinedsensor_location_1undefinedundefinedundefined " Why is it only referencing the first do ...

Creating a Custom Select Option Component with Ant Design Library

Is it possible to customize options in an antd select component? I have been trying to render checkboxes alongside each option, but I am only seeing the default options. Below are my 'CustomSelect' and 'CustomOption' components: // Cu ...

Using TypeScript to Limit the Generic Type Parameter Passed into a Function

I am working with a generic interface that has specific constraints applied to its parameters. Here is an example of how it is used: interface Fetcher<Params,Resp> { fetch(params: Params): Resp; }; In my code, I have a function that uses this inte ...