Как преобразовать объект JavaScript в гирляндной/точечной нотации в объект с вложенными объектами и массивами?

Я хочу развернуть такой объект...

var obj2 = {
    "firstName": "John",
    "lastName": "Green",
    "car.make": "Honda",
    "car.model": "Civic",
    "car.revisions.0.miles": 10150,
    "car.revisions.0.code": "REV01",
    "car.revisions.0.changes": "",
    "car.revisions.1.miles": 20021,
    "car.revisions.1.code": "REV02",
    "car.revisions.1.changes.0.type": "asthetic",
    "car.revisions.1.changes.0.desc": "Left tire cap",
    "car.revisions.1.changes.1.type": "mechanic",
    "car.revisions.1.changes.1.desc": "Engine pressure regulator",
    "visits.0.date": "2015-01-01",
    "visits.0.dealer": "DEAL-001",
    "visits.1.date": "2015-03-01",
    "visits.1.dealer": "DEAL-002"
};

... в объект с вложенными объектами и массивами, как показано ниже:

{
  firstName: 'John',
  lastName: 'Green',
  car: {
    make: 'Honda',
    model: 'Civic',
    revisions: [
      { miles: 10150, code: 'REV01', changes: ''},
      { miles: 20021, code: 'REV02', changes: [
        { type: 'asthetic', desc: 'Left tire cap' },
        { type: 'mechanic', desc: 'Engine pressure regulator' }
      ] }
    ]
  },
  visits: [
    { date: '2015-01-01', dealer: 'DEAL-001' },
    { date: '2015-03-01', dealer: 'DEAL-002' }
  ]
}

Вот моя (неудачная) попытка:

function unflatten(obj) {
    var result = {};

    for (var property in obj) {
        if (property.indexOf('.') > -1) {
            var substrings = property.split('.');

            console.log(substrings[0], substrings[1]);


        } else {
            result[property] = obj[property];
        }
    }

    return result;
};

Я быстро начал излишне повторять код, чтобы выполнить вложение объектов и массивов. Это определенно то, что требует рекурсии. Любые идеи?

РЕДАКТИРОВАТЬ: я также спросил об обратном, сгладить, в еще один вопрос.


person nunoarruda    schedule 09.03.2017    source источник
comment
У меня тут сильное дежавю. Могу поклясться, что читал этот вопрос вчера. -- Редактировать: хорошо, это противоположно вчерашний вопрос.   -  person John Weisz    schedule 09.03.2017
comment
Предыдущий вопрос был о выравнивании, этот о unflatten. Похоже на домашние задания   -  person Andrey    schedule 09.03.2017


Ответы (3)


Вы можете сначала использовать цикл for...in для зацикливания свойств объекта, затем разделить каждый ключ на ., а затем использовать сокращение для создания вложенных свойств.

var obj2 = {"firstName":"John","lastName":"Green","car.make":"Honda","car.model":"Civic","car.revisions.0.miles":10150,"car.revisions.0.code":"REV01","car.revisions.0.changes":"","car.revisions.1.miles":20021,"car.revisions.1.code":"REV02","car.revisions.1.changes.0.type":"asthetic","car.revisions.1.changes.0.desc":"Left tire cap","car.revisions.1.changes.1.type":"mechanic","car.revisions.1.changes.1.desc":"Engine pressure regulator","visits.0.date":"2015-01-01","visits.0.dealer":"DEAL-001","visits.1.date":"2015-03-01","visits.1.dealer":"DEAL-002"}

function unflatten(data) {
  var result = {}
  for (var i in data) {
    var keys = i.split('.')
    keys.reduce(function(r, e, j) {
      return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 == j ? data[i] : {}) : [])
    }, result)
  }
  return result
}

console.log(unflatten(obj2))

person Nenad Vracar    schedule 09.03.2017
comment
Строка 8 этого скрипта корявая, трудно разобрать, что происходит. Что делает r[e] = isNan(...? Перед тернаром логическое значение, возвращает ли что-то присваивание? - person ohsully; 18.07.2020
comment
@ohsully isNaN(Number(keys[j + 1])) проверяет, является ли следующий ключ числом или нет, при преобразовании из строки в число. Если он возвращает true, это означает, что это не число, а затем вы оцениваете другое условие, чтобы увидеть, является ли его последний ключ или нет, и присваиваете либо значение, либо пустой объект. Но если он возвращает false, то это число, а затем присваивается массив. - person Nenad Vracar; 18.07.2020
comment
Итак, если у вас такой ключ car.revisions.0.miles первые две части (автомобиль и ревизия) не являются числами, третья часть является числом, а последняя часть не является числом. - person Nenad Vracar; 18.07.2020

Попробуйте разбить проблему на две отдельные задачи:

  1. Установка значения по пути
  2. Зацикливание на объекте и распаковка ключей один за другим

Вы можете начать с функции setIn, которая будет выглядеть примерно так:

function setIn(path, object, value) {
  let [key, ...keys] = path; 

  if (keys.length === 0) {
    object[key] = value;
  } else {
    let nextKey = keys[0];
    object[key] = object[key] || isNaN(nextKey) ? {} : [];
    setIn(keys, object[key], value);
  }

  return object;
}

Затем объедините его с функцией unflatten, которая перебирает объект, выполняющий setIn для каждой клавиши.

function unflatten(flattened) {
  let object = {};

  for (let key in flattened) {
    let path = key.split('.');
    setIn(path, object, flattened[key]);
  }

  return object;
}

Конечно, для этого уже есть npm-пакет, и он также легко реализовать свои собственные, используя такие функции, как _.set из lodash.

Маловероятно, что вы когда-либо столкнетесь с достаточно длинным путем, в результате которого у вас закончатся кадры стека, но, конечно, можно реализовать setIn без рекурсии, используя циклы или батуты.

И, наконец, если вам нравятся неизменяемые данные и вы хотите работать с версией setIn, которая не изменяет ваши структуры данных, вы можете взглянуть на реализацию в Zaphod — библиотека JavaScript для обработки собственных структур данных, как если бы они были неизменяемыми.

person Dan Prince    schedule 09.03.2017

Вы можете пройти разделенный substrings и временный объект с проверкой, существует ли ключ, и создать новое свойство с проверкой следующего свойства, если оно является конечным числом, а затем назначить массив, иначе объект. Наконец, присвойте последней подстроке значение временному объекту.

function unflatten(obj) {
    var result = {}, temp, substrings, property, i;
    for (property in obj) {
        substrings = property.split('.');
        temp = result;
        for (i = 0; i < substrings.length - 1; i++) {
            if (!(substrings[i] in temp)) {
                if (isFinite(substrings[i + 1])) { // check if the next key is
                    temp[substrings[i]] = [];      // an index of an array
                } else {
                    temp[substrings[i]] = {};      // or a key of an object
                }
            }
            temp = temp[substrings[i]];
        }
        temp[substrings[substrings.length - 1]] = obj[property];
    }
    return result;
};

var obj2 = { "firstName": "John", "lastName": "Green", "car.make": "Honda", "car.model": "Civic", "car.revisions.0.miles": 10150, "car.revisions.0.code": "REV01", "car.revisions.0.changes": "", "car.revisions.1.miles": 20021, "car.revisions.1.code": "REV02", "car.revisions.1.changes.0.type": "asthetic", "car.revisions.1.changes.0.desc": "Left tire cap", "car.revisions.1.changes.1.type": "mechanic", "car.revisions.1.changes.1.desc": "Engine pressure regulator", "visits.0.date": "2015-01-01", "visits.0.dealer": "DEAL-001", "visits.1.date": "2015-03-01", "visits.1.dealer": "DEAL-002" };

console.log(unflatten(obj2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Чуть более компактная версия могла бы быть такой

function unflatten(object) {
    var result = {};
    Object.keys(object).forEach(function (k) {
        setValue(result, k, object[k]);
    });
    return result;
}

function setValue(object, path, value) {
    var way = path.split('.'),
        last = way.pop();

    way.reduce(function (o, k, i, kk) {
        return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
    }, object)[last] = value;
}

var obj2 = { "firstName": "John", "lastName": "Green", "car.make": "Honda", "car.model": "Civic", "car.revisions.0.miles": 10150, "car.revisions.0.code": "REV01", "car.revisions.0.changes": "", "car.revisions.1.miles": 20021, "car.revisions.1.code": "REV02", "car.revisions.1.changes.0.type": "asthetic", "car.revisions.1.changes.0.desc": "Left tire cap", "car.revisions.1.changes.1.type": "mechanic", "car.revisions.1.changes.1.desc": "Engine pressure regulator", "visits.0.date": "2015-01-01", "visits.0.dealer": "DEAL-001", "visits.1.date": "2015-03-01", "visits.1.dealer": "DEAL-002" };

console.log(unflatten(obj2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

person Nina Scholz    schedule 09.03.2017