【问题标题】:Iterate Complex JSON Object of Unknown complexity迭代未知复杂度的复杂 JSON 对象
【发布时间】:2020-05-14 11:38:12
【问题描述】:

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

我有一个复杂度未知的 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>

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

所以我的问题很简单:

我的递归哪里出错了?

【问题讨论】:

    标签: javascript html json recursion iteration


    【解决方案1】:

    有点学术练习,但这里是使用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}
    &lt;script src="https://bundle.run/object-scan@16.0.2"&gt;&lt;/script&gt;

    免责声明:我是object-scan的作者

    【讨论】:

      【解决方案2】:

      此代码假定字符串将成为 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';
      }
      &lt;section id='section'&gt;&lt;/section&gt;

      【讨论】:

        【解决方案3】:

        你可以使用这个非常简单的一行函数render

        注意:如果你想让 div 属性(id、class、data-type)在同一行,只需将所有第一个验证放在同一行

        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))

        【讨论】:

        • 好吧,这有点太聪明了,但令人惊讶的是也很干净。为了便于阅读,我会将它变成一个适当的函数,并去掉所有这些三元组并简单地用变量替换它们,否则使用字符串模板会使 HTML 结构非常明显。干得好
        • 谢谢兄弟。我真的很感谢你的评论。我有点习惯使用三元,但我同意其他不习惯它的人会觉得有点困惑
        【解决方案4】:

        您的原始 sn-p 可以很容易地工作 - 您输入格式中的子元素存储为数组,因此您只需在创建子 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;
        }
        

        【讨论】:

        • 太棒了...谢谢...我现在知道我的错误在哪里了。
        【解决方案5】:

        很抱歉,我觉得您的方法有点难以理解。我认为您应该尽可能多地使用 DOM API,并考虑到输入对象的潜在复杂性,尽可能保持代码简洁明了。

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

        这对我来说似乎很优雅:)

        希望对你有帮助。

        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)

        【讨论】:

          【解决方案6】:

          为简单起见,我使用 HTML concat 而不是创建元素。

          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)
          &lt;div id='content'&gt;&lt;/div&gt;

          【讨论】:

          • 嗨...感谢您的回答...我认为您的解决方案假定只有某些divs 将具有ids 或classes,但事实并非如此。任何 div 都可以有一个 id 和/或类。而且看起来这假定行总是在外面,而列在里面,这可以颠倒过来。此外,该结构可以任意深(或浅)。我已经更新了我的问题以反映这一点。谢谢。
          猜你喜欢
          • 2011-05-23
          • 2018-08-19
          • 2016-02-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-16
          • 1970-01-01
          • 2020-01-05
          相关资源
          最近更新 更多