Javascript how to loop over a json array where we don't know how many nested array there is inside?

帅比萌擦擦* 提交于 2021-02-15 05:38:02

问题


UPDATED SCRIPT

Here is an updated script I am working on:

function recursive(data, append_name) {
  for (parent_key in data) {
    var dim_type = data[parent_key]["type"];
    var dim_label = "";
    var dim_name = data[parent_key]["name"];
    if (typeof(data[parent_key]["label"])=="object") {
        dim_label = data[parent_key]["label"]["english"];
    }
    else {
        dim_label = data[parent_key]["label"];
    }
    
    for (child_key in data[parent_key]["children"]){
        //console.log(data[parent_key]["children"][child_key])
      var child_label = data[parent_key]["children"][child_key]["label"]["english"];

      if (append_name == "" || append_name == undefined) {
        var child_name = data[parent_key]["children"][child_key]["name"];
      } else {
        var child_name = append_name+"/"+data[parent_key]["children"][child_key]["name"];
      }
      if("children" in data[parent_key]["children"][child_key]) {
        recursive(data[parent_key]["children"][child_key]["children"], dim_name)
      }
      else {
        outputArray.push({"dim_label": dim_label,
                                            "dim_name": dim_name,
                          "child_name": dim_name+"/"+child_name,
                          "child_label": child_label})
      }
      console.log(outputArray, "")
    }
    //console.log(key, dim_label, dim_name, dim_type);
  }
}

The result is only showing 3 records out of 6, which are the first 3 rows only.

Here is a fiddle.

END OF EDIT


ORIGINAL QUESTION

I have JSON file I need to run a script over it to get 4 main fields:

  1. dim_label
  2. dim_name
  3. field_label
  4. field_name

The structure of the JSON array is as follows:

{
    "name": "Info",
    "title": "Info",
    "default_language": "default",
    "id_string": "...",
    "type": "survey",
    "children": [
        {
            "type": "text",
            "name": "basic_info",
            "label": "Basic Info",
            "children": [
                {
                    "type": "text",
                    "name": "name",
                    "label": {
                        "english": "What is your name"
                    }
                },
                {
                    "type": "text",
                    "name": "address",
                    "label": {
                        "english": "What is your address?"
                    }
                }
            ]
        },
        {
            "type": "text",
            "name": "more_data",
            "label": "More Data",
            "children": [
                {
                    "type": "text",
                    "name": "favourite_food",
                    "label": {
                        "english": "What is your favourite food?"
                    }
                },
                {
                    "type": "text",
                    "name": "favourite_destination",
                    "label": {
                        "english": "What is your favourite destination?"
                    },
                    "children": [
                        {
                            "type": "text",
                            "name": "france",
                            "label": {
                                "english": "France"
                            },
                            "type": "text",
                            "name": "usa",
                            "label": {
                                "english": "USA"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "type": "number",
            "name": "estimated_income",
            "label": "What is your annual estimated income?"
        }
    ]
}

And the desired output should look like that:

Note that the last record is having the fields similar because this is no children array inside.

Notice that for the favourite_destination part, there a children array inside another one that's why at the end there is the following fields:

more_data/favourite_destination/france

more_data/favourite_destination/usa

So the field_name at the end will hold the full path of its parents names.

I tried this JavaScript script:

function recursive(arr, dim_label, dim_name){
    
    for (prop in arr) {
    var title = arr["title"];
    if (prop == "children") {
        for (child in arr[prop]){
        var dim_name = "";
        var dim_label = "";
        var field_name = "";
        var field_label = "";
        dim_name = arr[prop][child]["name"] +"/"+dim_name;
        type = arr[prop][child]["type"];
        field_name = dim_name+arr[prop][child]["name"];
        dim_label =arr[prop][child]["name"]["english"];
        field_label = "";
        if ("label" in arr[prop][child] ) {
         dim_label  = arr[prop][child]["label"]["english"];
         field_label = arr[prop][child]["label"]["english"]
        }
        else {
            dim_label = dim_name;
        }
        array.push({label: dim_label, name: dim_name});
        /* if (type != "calculate" && type != "select one") {
          if ("children" in arr[prop][child]) {
            recursive(arr[prop][child], dim_label, dim_name);
          }
        } */
        console.log(dim_name, dim_label, field_name, field_label)
      }
    }
  }
}

And the result was only 3 recrods:

"basic_info/", undefined, "basic_info/basic_info", undefined

"more_data/", undefined, "more_data/more_data", undefined

"estimated_income/", undefined, "estimated_income/estimated_income", undefined

Here is a jsfiddle.

How can I loop over an array that I don't know how many nested arrays there is inside to get the required information?


回答1:


Here's an approach which separates the data traversal from the output formatting:

const getPaths = (obj) =>
  obj .children
    ? obj .children .flatMap (getPaths) .map (p => [obj, ...p])
    : [[obj]]

const extract = (data) =>
  getPaths (data) .map ((path) => ((
    field = path [path .length - 1], 
    parent = path .length > 2 ? path [path .length - 2] : path [path .length - 1]
  ) => ({
    dim_label: parent .label .english || parent .label,
    dim_name: path .slice (1, path .length > 2 ? -1 : Infinity) .map (n => n.name) .join('/'),
    field_label: field .label .english || field .label,
    field_name: path .slice(1) .map (n => n .name) .join('/')
  }))())

const data = {name: "Info", title: "Info", default_language: "default", id_string: "...", type: "survey", children: [{type: "text", name: "basic_info", label: "Basic Info", children: [{type: "text", name: "name", label: {english: "What is your name"}}, {type: "text", name: "address", label: {english: "What is your address?"}}]}, {type: "text", name: "more_data", label: "More Data", children: [{type: "text", name: "favourite_food", label: {english: "What is your favourite food?"}}, {type: "text", name: "favourite_destination", label: {english: "What is your favourite destination?"}, children: [{type: "text", name: "france", label: {english: "France"}}, {type: "text", name: "usa", label: {english: "USA"}}]}]}, {type: "number", name: "estimated_income", label: "What is your annual estimated income?"}]}

console .log (extract (data))

const display = (objs) => `<table><thead><tr>${Object.keys(objs[0]).map(k => `<th>${k}</th>`).join('')}</tr></thead><tbody>${objs.map(o => `<tr>${Object.values(o).map(v => `<td>${v}</td>`).join('')}</tr>`).join('')}</tbody></table>`

document.getElementById('output').innerHTML = display (extract (data))
.as-console-wrapper {max-height: 50% !important; bottom: 0}
table {border-collapse: collapse}
td, th { border: 1px solid #ccc}
th {background: #eee}
<div id="output"></div>

getPaths turns a nested object like this into an array of paths, each path being the list of objects down the tree of children to end at a leaf node, defined here as one that has no children property.

Our main function, extract calls getPaths and then maps the resulting paths into objects by finding the last two nodes as our field and parent (dim?) objects, extracting the relevant data from those and from the entire path into new objects.

We demonstrate this both by logging this list of objects and calling a display function which turns the data into an HTML table.

Note that the complexities in the output field definitions speaks to some strong inconsistencies in your data. We need to check something. label .english and default to something .label if it doesn't exist. We need to ignore the outermost container when we list paths. We need to do odd handling for paths that have only one node and that outermost container. If you have any control over that data format, I'd suggest that it would be worthwhile to do some cleanup.

Update

User Thankyou points out that this might be a bit simpler if we used a call function rather than the IIFE in the above.

This version, using the same getPaths function, should work equally well:

const call = (fn, ...args) => 
  fn (...args)

const extract = (data) =>
  getPaths (data) .map ((path) => call ((
    field = path [path .length - 1], 
    parent = path .length > 2 ? path [path .length - 2] : path [path .length - 1]
  ) => ({
    dim_label: parent .label .english || parent .label,
    dim_name: path .slice (1, path .length > 2 ? -1 : Infinity) .map (n => n.name) .join('/'),
    field_label: field .label .english || field .label,
    field_name: path .slice(1) .map (n => n .name) .join('/')
  })))

It could also be written like this:

const extract = (data) =>
  getPaths (data) .map (
    (path) => call 
      ((field, parent) => ({
        dim_label: parent .label .english || parent .label,
        dim_name: path .slice (1, path .length > 2 ? -1 : Infinity) .map (n => n.name) .join('/'),
        field_label: field .label .english || field .label,
        field_name: path .slice(1) .map (n => n .name) .join('/')
      }),
      path [path .length - 1],
      path .length > 2 ? path [path .length - 2] : path [path .length - 1]
  ))



回答2:


In your case:

const array = []
function recursive(arr, dim_label, dim_name){
    const title =  arr.title;
    for (prop in arr) {
        const title =  arr.title;
        if (prop === "children") {
            for (child in arr[prop]){
                let dim_name = arr[prop][child]["name"] +"/" // +dim_name;
                type = arr[prop][child]["type"];
                let field_name = dim_name+arr[prop][child]["name"];
                let dim_label;
                let field_label;
                if ("label" in arr[prop][child]) {
                    if (arr[prop][child]["label"].english){
                        dim_label  = arr[prop][child]["label"]["english"];
                        field_label = arr[prop][child]["label"]["english"]
                    } else {
                        dim_label  = arr[prop][child]["label"];
                        field_label = arr[prop][child]["label"];
                    }
                } else {
                    dim_label = dim_name;
                }
                array.push({label: dim_label, name: dim_name});
                recursive({children: arr[prop][child].children}, dim_label, dim_name); 
            }
        }
    }
}
recursive(data)

┌─────────┬─────────────────────────────────────────┬──────────────────────────┐
│ (index) │                  label                  │           name           │
├─────────┼─────────────────────────────────────────┼──────────────────────────┤
│    0    │              'Basic Info'               │      'basic_info/'       │
│    1    │           'What is your name'           │         'name/'          │
│    2    │         'What is your address?'         │        'address/'        │
│    3    │               'More Data'               │       'more_data/'       │
│    4    │     'What is your favourite food?'      │    'favourite_food/'     │
│    5    │  'What is your favourite destination?'  │ 'favourite_destination/' │
│    6    │                  'USA'                  │          'usa/'          │
│    7    │ 'What is your annual estimated income?' │   'estimated_income/'    │
└─────────┴─────────────────────────────────────────┴──────────────────────────┘

Base way to solve is:

const data = [
    {
        name: "a",
        children:[
            {
                name: "b",
                children:[
                    {
                        name: "c"
                    },
                    {
                        name: "d"
                    },
                    {
                        name: "e",
                        children: [
                            {name: "f"},
                            {name: "g"},
                        ]

                    }
                ]
            }
        ]
    }
]

const recursiveWalk = (arr, path) => {
   if (!arr) return;
   for(let row = 0; row < arr.length; ++row ){
        console.log(
            arr[row].name, 
            'path:', (path ? path + '\\' : '')  + arr[row].name, 
            )
        recursiveWalk(arr[row].children, (path ? path + '\\' : '') +  arr[row].name)     
   }
   return ''
}

recursiveWalk(data, "")

I don't quite understand why there is recursion where the data is deepened

const data = {
    "name": "Info",
    "title": "Info",
    "default_language": "default",
    "id_string": "...",
    "type": "survey",
    "children": [
        {
            "type": "text",
            "name": "basic_info",
            "label": "Basic Info",
            "children": [
                {
                    "type": "text",
                    "name": "name",
                    "label": {
                        "english": "What is your name"
                    }
                },
                {
                    "type": "text",
                    "name": "address",
                    "label": {
                        "english": "What is your address?"
                    }
                }
            ]
        },
        {
            "type": "text",
            "name": "more_data",
            "label": "More Data",
            "children": [
                {
                    "type": "text",
                    "name": "favourite_food",
                    "label": {
                        "english": "What is your favourite food?"
                    }
                },
                {
                    "type": "text",
                    "name": "favourite_destination",
                    "label": {
                        "english": "What is your favourite destination?"
                    },
                    "children": [
                        {
                            "type": "text",
                            "name": "france",
                            "label": {
                                "english": "France"
                            },
                            "type": "text",
                            "name": "usa",
                            "label": {
                                "english": "USA"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "type": "number",
            "name": "estimated_income",
            "label": "What is your annual estimated income?"
        }
    ]
}

const table=[]

for (i = 0; i < data.children.length; i++ ) {
    const item = data.children[i]
    if (data.children[i].children){
        for (n=0; n< data.children[i].children.length; n++ ){
            const row = [item.label, item.name, item.children[n].label.english,`${item.name}/${item.children[n].name}`]
            table.push(row)
        }
    } else {
        const row = [item.label, item.name, item.label, item.name]; 
        table.push(row)
    }
}

console.table(table)
const tableNode = document.querySelector('#table');

for (let row = 0; row < table.length; ++row ){
   const rowNode = document.createElement('tr');
   for (let col = 0; col < table[row].length; ++col){
      cell = document.createElement('td');
      cell.textContent=table[row][col];
      rowNode.appendChild(cell);
   }
   tableNode.appendChild(rowNode); 
}
  table { 
    border: 1px double black; /* Рамка вокруг таблицы */
    border-collapse: collapse; /* Отображать только одинарные линии */
   }
<table id="table" border="1">
<table>


来源:https://stackoverflow.com/questions/66046904/javascript-how-to-loop-over-a-json-array-where-we-dont-know-how-many-nested-arr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!