问题
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:
- dim_label
- dim_name
- field_label
- 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