Слияние JSON и вставить в DOM


Я начинающий младший Дэв. Это модифицированная версия кода, которую я написал для технического интервью, которое я сделал сегодня (не прошло. Грустно.). Проблема была в том, чтобы принести два JSON файлы, а также отображать их данные на столе. Нет в jQuery, только простые ООН-редактируемые HTML-файл.

Я ищу:

  • Способов я мог бы сделать код более читаемым, улучшить имена переменных, комментирование.
  • Проблемы безопасности при парентеральном введении данных в DOM
  • Это шаблон, пытаясь нормализовать данные правильные?
  • Это хорошо для карри функции, как это?
  • Я должен мемоизированную пользовательских данных в хранилище?
  • (Только если это разрешено на КЛ) советы для интервью вопрос такой.

const apiAddress = 'https://api.myjson.com/bins/'
const tableSchema = {short_name: 'User', logins: 'Logins', attempts: 'Attempts'}
// JSON data stored at
// https://api.myjson.com/bins/14pl91
// https://api.myjson.com/bins/xkdzp

/**
 * Fetch should be polyfilled if supporting older browsers
 * 
 * (String) => Object
 * @param {String} path The localpath and filename of the json file
 * @returns {Object}
 */
const fetchData = (path) => {
  return fetch(apiAddress + path)
    .then(res => {
      return res.json()
    }).catch(console.error)
}

/**
 * Use this: https://github.com/paularmstrong/normalizr instead
 * 
 * (String) => (Array) => Object
 * @param {string} key the key used to tell if different objects are representing the same thing
 * @param {[Object<{key: String}>]} objectArray an array of objects, each object must contain the key as a property
 * @returns {Object<{[key: String]: Object}>} Returns an object that contains the input objects mapped by the key
 */
const normalizer = (key) => (objectArray) => {
  return objectArray.reduce((acc, cur) => {
      if (cur.hasOwnProperty(key)) {
        return Object.assign(acc, {[cur[key]]: {...cur}})
      }
    }, {})
}

/**
 * ([Object]) => Object
 * @param {[Object<{[key: String]: Object}>]} objectArray An array of keyed (normalized) objects that will be merged by said key
 * @returns {Object<{[key: String]: Object}>} The merged object
 */
const mergeObjects = (objectArray) => {
  return objectArray.reduce((acc, cur) => {
    Object.keys(cur).map((key) => {
      acc[key] = Object.assign(cur[key], acc[key])
    })
    return acc
  }, {})
}

/**
 * Should place these table functions within a class, prototypes, component
 * 
 * (schema: Object<ITableSchema>) => Table
 * @param {Object<ITableSchema>} schema The schema the table will conform to
 * @returns {Table} A new table instance
 */
const createTable = (schema) => {
  const table = document.createElement('table')
  const headerRow = table.createTHead().insertRow()
  
  // Add the titles in each column
  obj2Arr(schema).forEach((title) => {
    const th = document.createElement('th')
    th.innerText = title
    headerRow.appendChild(th)
  })
  
  return table
}

/**
 * (table: Table, fields: [String]) => (entry: Object<any>) => void
 * @param {Table} table Adds rows to a table reference
 * @param {[String]} fields Fields is an ordered array of keys to get from the entry data
 * @param {Object<any>} entry Data that is to be inserted into the row cells
 */
const createRowInTable = (table, fields) => (entry) => {
  const row = table.insertRow()
  fields.forEach((field) => {
    const cell = row.insertCell()
    cell.innerText = entry[field]
  })
}

/**
 * Utils
 * Makes everything below a bit cleaner and easier to follow
 * Would like to place these in separate files or use lodash
 *
 * @param {Object} object A mapped object
 * @returns {Array} An array without mappings
 */
const obj2Arr = (object) => Object.keys(object).map((key) => object[key])

/**
 * Filters objects that don't contain all keys
 *
 * @param {[String]} keys Keys to check exist in the object
 * @param {Object} object Object that will be checked contains keys
 * @returns {Boolean}
 */
const objFilter = (keys, object) =>
  keys.reduce((acc, cur) => {
    return acc ? object.hasOwnProperty(cur) : false
  }, true)

/**
 * init() main() <- for people that cmd+f
 */
Promise.all([
  fetchData('14pl91'),
  fetchData('xkdzp')
]).then(res => {

  // Manually strip the response, ugly but can't think of a better way
  const strippedResponse = res.map((ele) => {
    if (ele.hasOwnProperty('users')) return ele.users
    if (ele.hasOwnProperty('user_stats')) return ele.user_stats
  })

  // Process the response
  const normalizedUsers = strippedResponse.map(normalizer('id'))
  const users = mergeObjects(normalizedUsers)

  // Filters users without the requisite fields
  const columnFilter = objFilter.bind({}, Object.keys(tableSchema)) // To show PA instead of currying
  const filteredUsers = obj2Arr(users).filter(columnFilter)

  // Build the table
  const table = createTable(tableSchema)
  
  // Put the filtered users inside the table
  const createUserRow = createRowInTable(table, Object.keys(tableSchema))
  filteredUsers.forEach(createUserRow)

  // Find the table's injection point
  const container = document.getElementsByClassName('json-table')[0]
    .lastElementChild
    .lastElementChild

  // Place table inside DOM
  container.appendChild(table)
})
<!DOCTYPE html>
<html>
<head>
 <script src="script.js"></script>
</head>
<body>
  <div class="json-table">
    <h3>JSON Table</h3>
    <div class="module-border">
      <div class="module-content">
      </div>
    </div>
  </div>
</body>
</html>

Примечание: Я удалил все упоминания о компании и мутировал данных, сохраняя при этом структуру и проблемы идентичны.

Редактировать

Спецификация Вопрос
  • Извлечения содержимого пользователями в JSON.JSON и статистика.в JSON
  • Цель таблицы-показать статистику пользователей. Если пользователь не имеет статов, не показывать их.
  • Разбора JSON-файлы и отображать данные с помощью таблицы, как показано ниже, используя short_name, loginsи attempts поля.


Комментарии
1 ответ

const fetchData = (path) => {
return fetch(apiAddress + path)
.then(res => {
return res.json()
}).catch(console.error)
}

Проблема в том, что вызывающая fetchData не осознает ошибку, поскольку catch сделал свою работу - ловить ошибки. Не поймать, а если это для журнала, поймать и повторно. Разрешить звонящему разобраться с отказом.


// Manually strip the response, ugly but can't think of a better way
const strippedResponse = res.map((ele) => {
if (ele.hasOwnProperty('users')) return ele.users
if (ele.hasOwnProperty('user_stats')) return ele.user_stats
})

Это делает код знают о наличии "пользователи" и "user_stats". Вы могли бы перенести в ту часть кода, который был все-таки специфичный, перемещая его от более общих частей. Например, липкость на другой then на выборку, извлекающий собственность.


// Process the response
const normalizedUsers = strippedResponse.map(normalizer('id'))
const users = mergeObjects(normalizedUsers)

Вы не должны нормализовать обоих массивов. Что в итоге происходит ты обернулся массив в объект, но затем распаковал объекта в массив с mergeObjects. Много упаковки и распаковки происходит. Ты мог бы превратить только один из них в качестве поиска, а вы карту через другой.


// Build the table
const table = createTable(tableSchema)

// Put the filtered users inside the table
const createUserRow = createRowInTable(table, Object.keys(tableSchema))
filteredUsers.forEach(createUserRow)

// Find the table's injection point
const container = document.getElementsByClassName('json-table')[0]
.lastElementChild
.lastElementChild

// Place table inside DOM
container.appendChild(table)

Это было совершенно лишним. Правда, это будет правильный путь™, чтобы сделать это в JS, но, поскольку требование было одно-время рендеринга, вы могли бы просто построил таблицы в виде строки и использовать innerHTML.


В общем, нет ничего плохого в коде. Вот что я бы сделал вначале, раскладывая вещи, как они приходят на ум (группировка, объединение, фильтрация, рендеринг). Это когда вы берете второй взгляд, и после того, как вы учли другие факторы, когда вы начинаете оптимизировать.



const container = document.getElementById('stats')
const users = fetch('https://api.myjson.com/bins/14pl91').then(r => r.json()).then(r => r.users)
const userStats = fetch('https://api.myjson.com/bins/xkdzp').then(r => r.json()).then(r => r.user_stats)

Promise.all([users, userStats]).then(([users, userStats]) => {
// Convert stats into a lookup
const statsLookup = userStats.reduce((c, v) => (c[v.id] = v, c), {})

const userData = users
// Remove those users with no stats
.filter(u => statsLookup.hasOwnProperty(u.id))
// Grab the data we need
.map(u => ({
short_name: u.short_name,
logins: statsLookup[u.id].logins,
attempts: statsLookup[u.id].attempts
}))

// Render
container.innerHTML = `
<table border="1">
<thead>
<tr>
<th>User</th>
<th>Logins</th>
<th>Attempts</th>
</tr>
</thead>
<tbody>
${userData.map(u => `
<tr>
<td>${u.short_name}</td>
<td>${u.logins}</td>
<td>${u.attempts}</td>
</tr>
`).join('')}
</tbody>
</table>
`
}).catch(e => {
// Something went wrong. Do something.
})


<div id="stats"></div>



4
ответ дан 16 февраля 2018 в 02:02 Источник Поделиться