-1

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:

enter image description here

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?

alim1990
  • 4,656
  • 12
  • 67
  • 130

2 Answers2

2

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]
  ))
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • any reasoning behind the iife instead of your recent adoption of `call`? – Mulan Feb 04 '21 at 17:48
  • 1
    @Thankyou: No, no good reason. If I'm using a number of small helper functions, I will include `call` among them. If I have only a few larger functions, I might prefer an IIFE. But there's no real logic behind that peccadillo. – Scott Sauyet Feb 04 '21 at 18:31
  • and you taught me a new word lmao ^^ – Mulan Feb 04 '21 at 18:33
  • @Thankyou. It's only fair as you just introduced [combinatronics](https://stackoverflow.com/q/66045980/#comment116780514_66049126), which even if it's only a typo makes for a wonderful word! – Scott Sauyet Feb 04 '21 at 18:47
  • Scott see [wikipedia: Combinatronics](https://en.wikipedia.org/wiki/Combinatorics). Programming/math is full of all sorts of fun words like this ^^ – Mulan Feb 04 '21 at 19:06
  • 1
    @Thankyou: Fixed to add `call` options. Also, I think it was a typo, `"...tronics"` vs `"...torics"`. But it sounded to me like a portmanteau of "combinatorics" and "histrionics", which is lovely. My masters is in Math, with a focus on combinatorics, and I had several professors prone to histrionics, so the combination sounds amazing. – Scott Sauyet Feb 04 '21 at 19:11
  • Notice sir that from my array example, it is taking only usa from the children and discarding france – alim1990 Feb 05 '21 at 06:40
  • @alim1990: As far as I can tell, I'm matching your [output table](https://i.stack.imgur.com/0wpii.png) precisely. You might need to use the full-screen link on the snippet to see both the table and the console completely. The data the function returns is key. The table output is simply thrown together. – Scott Sauyet Feb 05 '21 at 13:57
  • @alim1990: Never mind. I think I know what you're talking about. Your input is missing several braces, which makes the USA fields override the France ones in a single object. (In other words, `favorite_destination` has only one child.) I fixed that in my snippet. You should probably fix it in the question as well. – Scott Sauyet Feb 05 '21 at 14:00
1

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>
Daniil Loban
  • 4,165
  • 1
  • 14
  • 20
  • It is really needed sir for our implementation, there is 2 missing records from your script output. So any help? Please check my updated script in 2 min. – alim1990 Feb 04 '21 at 15:06
  • I also updated my answer, look at first snippet – Daniil Loban Feb 04 '21 at 15:25
  • Still not what I want, because all under path should be `a\b\c`, `a\b\d`, `a\b\e\f` and `a\b\e\g`. – alim1990 Feb 04 '21 at 15:30
  • I think it is working good, but it still missing the label and name of original path of each child path. I will appreciate if you helped e with it. – alim1990 Feb 04 '21 at 16:01