Итерационный Комплекс JSON Объект Неизвестной сложности - PullRequest
0 голосов
/ 29 января 2020

Это должно быть легкой проблемой, но по какой-то причине я бью кирпичную стену.

У меня есть JSON объект неизвестной сложности, который может содержать другие объекты, массивы или свойства строки .

Например, у меня может быть такой объект:

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

* Примечание (для ясности): объект может иметь сильно различающуюся структуру. У него могут быть перевернутые строки и столбцы, или они могут быть вложены глубже, или их нет вообще. Могут быть или не быть идентификаторы или классы на любом из объектов *

Мне нужно перебрать этот объект и "преобразовать" его в 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 с, они просто как-то поворачиваются.

Итак, мой вопрос прост:

Где я ошибаюсь в своей рекурсии?

Ответы [ 5 ]

2 голосов
/ 29 января 2020

Вы можете использовать эту очень простую однострочную функцию 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))
1 голос
/ 29 января 2020

Этот код предполагает, что строка станет атрибутом 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>
1 голос
/ 29 января 2020

Ваш исходный фрагмент кода можно легко заставить работать - дочерние элементы в вашем формате ввода хранятся в виде массивов, поэтому вам просто нужно перебрать их перед созданием дочерних элементов 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;
}
1 голос
/ 29 января 2020

Извините, но мне трудно понять ваш подход. Я думаю, что вам следует как можно больше использовать DOM API и, учитывая потенциальную сложность вашего входного объекта, сохранять свой код как можно более чистым и понятным.

Я бы предложил использовать рекурсивный подход, при котором вы обрабатывать один элемент за раз со всеми деталями (id, class, et c), затем его дочерние элементы и добавлять их перед возвратом нового элемента обратно. Таким образом, вы также можете убедиться, что любая глубина покрыта.

Мне это кажется довольно элегантным:)

Надеюсь, это поможет.

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)
0 голосов
/ 29 января 2020

Для простоты я использую 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)
<div id='content'></div>
...