迭代复杂度未知的复杂JSON对象

时间:2020-01-29 00:52:51

标签: javascript html json recursion iteration

这应该是一个简单的问题,但是由于某种原因,我碰到了砖墙。

我有一个复杂程度未知的JSON对象,其中可能包含其他对象,数组或字符串属性。

作为一个例子,我可能有一个像这样的对象:

let json = {
    id: "foo",
    rows: [
        {
            class: "blah",
            cols: [
                {
                    class: "haha"
                },{
                    class: "bar"
                },{
                    class: "baz"
                }
            ]
        },
        {
            class: "yada"
        }
    ]
}

*注(为清楚起见):对象的结构可能大不相同。它的行和列可能颠倒了,或者嵌套得更深了,或者根本没有。在任何对象上可能有或没有ID或类*

我需要遍历该对象并将其“转换”为html结构,如下所示:

<div id = "foo" data-type = "rows">
    <div class = "blah" data-type = "cols">
        <div class = "haha"></div>
        <div class = "bar"></div>
        <div class = "baz"></div>
    </div>
    <div class = "yada"></div>
</div>

每个对象都是一个(可能是嵌套的)div,字符串属性是该div上的属性,而数组键则表示类型。

这是我目前所拥有的:

function iterateJsonToHTML(frame)  {
    let content = document.createElement("div");
     let iterator = function(frame, content)  {
        for (let [key, value] of Object.entries(frame))  {
            if (Array.isArray(value)  ||  (typeof value === "object"  &&  value !== null) )  {
                if (Array.isArray(value))  {
                    iterator(value, content);
                }  else if (typeof value === "object")  {
                    let element = document.createElement("div");
                    content = content.appendChild(element);
                     iterator(value, content);
                }
            }  else {
                // dealing with attributes here.  Not sure how to parse multiple of these.
            }
        }
    };
    iterator(frame, content);
    return content;
}

这将创建一个HTML结构,而不是我期望的结构:

<div>
    <div>
        <div>
            <div>
                <div></div>
            </div>
        </div>
        <div></div>
    </div>
</div>

div的数量正确,它们只是以某种方式旋转了。

所以我的问题很简单:

我的递归哪里出问题了?

6 个答案:

答案 0 :(得分:2)

您可以使用此非常简单的单行功能render

注意:如果要在同一行中使用div属性(id,class,数据类型),只需将所有第一个验证都放在同一行

const obj = {
    id: "foo",
    rows: [
        {
            class: "blah",
            cols: [
                {
                    class: "haha"
                },{
                    class: "bar"
                },{
                    class: "baz"
                }
            ]
        },
        {
            class: "yada"
        }
    ]
}
const render = (obj) =>  
  `
    <div 
      ${ obj.class ? `class="${obj.class}"` : '' }
      ${ obj.id ? `id="${obj.id}"` : '' }
      ${ obj.rows 
          ? 'data-type="rows"' 
          : obj.cols 
            ? 'data-type="cols"' 
            : '' }
    >
      ${
        (obj.cols || obj.rows || [])
          .map(childObj =>
            render(childObj)
          ).join('')
      }
    </div>
  `
console.log(render(obj))

答案 1 :(得分:1)

对不起,但是我发现您的方法有点难以理解。我认为您应该尽可能使用DOM API,并且考虑到输入对象的潜在复杂性,请保持代码尽可能的清晰明了。

我建议使用递归方法,其中一次处理一个元素的所有详细信息(id,class等),然后处理其子元素,并在返回新元素之前附加这些子元素。这样,您还可以确保覆盖任何深度。

对我来说似乎很优雅:)

希望有帮助。

let json = {
  id: "foo",
  rows: [
    {
      class: "blah",
      cols: [
        { class: "haha"},
        { class: "bar" },
        { class: "baz" }
      ]
    }, {
      class: "yada"
    }
  ]
}

function transform(element) {
  const { id, rows, cols, class: className } = element
  const $element = document.createElement('div')

  if ( id ) $element.setAttribute('id', id)
  if ( className ) $element.classList.add(className)
  
  let children = []
  
  if ( rows ) {
    $element.dataset.type = 'rows'
    children = rows.map(transform)
  }
  
  if ( cols ) {
    $element.dataset.type = 'cols'
    children = cols.map(transform)
  }
  
  children.forEach(function append(child) {
    $element.appendChild(child)
  })
  
  return $element
}

const $json = transform(json)
console.log($json.outerHTML)

答案 2 :(得分:1)

您的原始代码段很容易使用-输入格式中的子元素存储为数组,因此您只需要在创建子div之前对其进行迭代:

function iterateJsonToHTML(frame)  {
    let root = document.createElement("div");
    let iterator = function(frame, content)  {
        for (let [key, value] of Object.entries(frame))  {
            if (Array.isArray(value))  {
                content.setAttribute("data-type", key);
                for (let item of value) {
                    const element = document.createElement("div");
                    content.appendChild(element);
                    iterator(item, element);
                }
            }  else {
                content.setAttribute(key, value);
            }
        }
    };
    iterator(frame, root);
    return root;
}

答案 3 :(得分:1)

此代码假定字符串将成为div的属性。数组将变成几个div。

  • 该函数一次仅读取json的一个级别,从而创建div +属性。
  • 如果它跨数组运行,它将为每个数组元素递归自身。

function appendDOM( json, element ) {
  let child = document.createElement('div');
  
  for (const property in json) {
    if( typeof json[property] === 'string' ) {
      child.setAttribute( property, json[property] );
    } else if( Array.isArray( json[property] ) ) {
    	for( let i=0; i< json[property].length; i++ ) {
        appendDOM( json[property][i], child );
      }
    }
  }
  
  element.appendChild( child );
}


let json = {id: "foo",rows: [{class: "blah",cols: [{class: "haha"},{class: "bar"},{class: "baz"}]},{class: "yada", style:"background: yellow"}]}

appendDOM( json, document.getElementById('section') );
div{
  border: 1px solid red;
  padding: 5px;
  margin: 5px;
  text-align : center;
}
#foo::before{
  content: 'foo';
}
.blah::before{
  content: 'blah';
}
.haha::before{
  content: 'haha';
}
.bar::before{
  content: 'bar';
}
.baz::before{
  content: 'baz';
}
.yada::before{
  content: 'yada';
}
<section id='section'></section>

答案 4 :(得分:0)

为简单起见,我使用HTML concat代替元素创建。

default_args = {
    "owner": "airflow",
    "depends_on_past": False,
    'start_date': airflow.utils.dates.days_ago(n=0,minute=1),
    'on_failure_callback': send_task_failed_msg_to_slack,
    'sla': timedelta(minutes=1),
    "retries": 0, 
    "pool": 'canary',
    'priority_weight': 1
}

dag = airflow.DAG(
    dag_id='sla_test',
    default_args=default_args,
    sla_miss_callback=send_sla_miss_message_to_slack,
    schedule_interval='*/5 * * * *',
    catchup=False,
    max_active_runs=1,
    dagrun_timeout=timedelta(minutes=5)
)

def sleep():
    """ Sleep for 2 minutes """
    time.sleep(90)
    LOGGER.info("Slept for 2 minutes")

def simple_print(**context):
    """ Prints a message """
    print("Hello World!")


sleep = PythonOperator(
    task_id="sleep",
    python_callable=sleep,
    dag=dag
    )

simple_task = PythonOperator(
    task_id="simple_task",
    python_callable=simple_print,
    provide_context=True,
    dag=dag
    )

sleep >> simple_task

let json = {
  id: "foo",
  rows: [{
      class: "blah",
      cols: [{
        class: "haha"
      }, {
        class: "bar"
      }, {
        class: "baz"
      }]
    },
    {
      class: "yada"
    }
  ]
}

function iterateJsonToHTML(frame) {
  let html = "";
  html += '<div id = "' + frame.id + '" data-type = "rows">';
  for (let i = 0; i < frame.rows.length; i++) {
    html += '<div class = "' + frame.rows[i].class + '" data-type = "cols">';
    if (frame.rows[i].cols) {
      for (let j = 0; j < frame.rows[i].cols.length; j++) {
        html += '<div class = "' + frame.rows[i].cols[j].class + '"></div>';
      }
    }
    html += '</div>';
  }
  html += '</div>';
  document.getElementById('content').innerHTML = html;
}

iterateJsonToHTML(json);
console.log(document.getElementById('content').innerHTML)

答案 5 :(得分:0)

有点学术练习,但这里是使用 object-scan

的迭代解决方案

// const objectScan = require('object-scan');

const json = {
  id: 'foo',
  rows: [
    {
      class: 'blah',
      cols: [
        { class: 'haha' },
        { class: 'bar' },
        { class: 'baz' }
      ]
    },
    { class: 'yada' }
  ]
};

const r = objectScan(['', '**'], {
  reverse: false,
  beforeFn: (state) => {
    // eslint-disable-next-line no-param-reassign
    state.context = [];
  },
  afterFn: (state) => {
    state.result = state.context.join('\n');
  },
  breakFn: ({ depth, value, context }) => {
    if (typeof value !== 'string' && !Array.isArray(value)) {
      const properties = Object
        .entries(value)
        .map(([k, v]) => (typeof v === 'string' ? `${k} = "${v}"` : `data-type = "${k}"`));
      context.push(`${' '.repeat(depth)}<div ${properties.join(' ')}>`);
    }
  },
  filterFn: ({ depth, value, context }) => {
    if (typeof value !== 'string' && !Array.isArray(value)) {
      context.push(`${' '.repeat(depth)}</div>`);
    }
  }
})(json);

console.log(r);
/* =>
<div id = "foo" data-type = "rows">
    <div class = "blah" data-type = "cols">
        <div class = "haha">
        </div>
        <div class = "bar">
        </div>
        <div class = "baz">
        </div>
    </div>
    <div class = "yada">
    </div>
</div>
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@16.0.2"></script>

免责声明:我是object-scan

的作者