Один вкладыш для сглаживания вложенного объекта

Мне нужно сгладить вложенный объект. Нужен один лайнер. Не уверен, какой правильный термин для этого процесса. Я могу использовать чистый Javascript или библиотеки, мне особенно нравится подчеркивание.

У меня есть...

{
  a:2,
  b: {
    c:3
  }
}

И я хочу...

{
  a:2,
  c:3
}

Я пробовал...

var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "fred")
alert(JSON.stringify(resultObj));

Что работает, но мне также нужно, чтобы это работало...

var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "john")
alert(JSON.stringify(resultObj));

Ответы

Ответ 1

Ну вот:

Object.assign({}, ...function _flatten(o) { return [].concat(...Object.keys(o).map(k => typeof o[k] === 'object' ? _flatten(o[k]) : ({[k]: o[k]})))}(yourObject))

Сводка: рекурсивно создайте массив объектов с одним свойством, а затем объедините их все с Object.assign.

Это использует функции ES6, включая Object.assign или оператор распространения, но их должно быть достаточно просто переписать, чтобы не требовать их.

Для тех, кто не заботится о однострочном безумии и предпочел бы иметь возможность действительно читать его (в зависимости от вашего определения читаемости):

Object.assign(
  {}, 
  ...function _flatten(o) { 
    return [].concat(...Object.keys(o)
      .map(k => 
        typeof o[k] === 'object' ?
          _flatten(o[k]) : 
          ({[k]: o[k]})
      )
    );
  }(yourObject)
)

Ответ 2

Упрощенный читаемый пример, без зависимостей

/**
 * Flatten a multidimensional object
 *
 * For example:
 *   flattenObject({ a: 1, b: { c: 2 } })
 * Returns:
 *   { a: 1, c: 2}
 */
export const flattenObject = (obj) => {
  const flattened = {}

  Object.keys(obj).forEach((key) => {
    if (typeof obj[key] === 'object' && obj[key] !== null) {
      Object.assign(flattened, flattenObject(obj[key]))
    } else {
      flattened[key] = obj[key]
    }
  })

  return flattened
}

Рабочий пример: https://jsfiddle.net/webbertakken/jn613d8p/2/

Ответ 3

Это не совсем один лайнер, но вот решение, которое от ES6 ничего не требует. Он использует метод extend подчеркивания, который может быть заменен для jQuery.

function flatten(obj) {
    var flattenedObj = {};
    Object.keys(obj).forEach(function(key){
        if (typeof obj[key] === 'object') {
            $.extend(flattenedObj, flatten(obj[key]));
        } else {
            flattenedObj[key] = obj[key];
        }
    });
    return flattenedObj;    
}

Ответ 4

Вот ванильные решения, которые работают с массивами, примитивами, регулярными выражениями, функциями, любым количеством уровней вложенных объектов и практически всем остальным, что я могу на них бросить. Первый перезаписывает значения свойств так, как вы ожидаете от Object.assign.

((o) => {
  return o !== Object(o) || Array.isArray(o) ? {}
    : Object.assign({}, ...function leaves(o) {
    return [].concat.apply([], Object.entries(o)
      .map(([k, v]) => {
        return (( !v || typeof v !== 'object'
            || !Object.keys(v).some(key => v.hasOwnProperty(key))
            || Array.isArray(v))
          ? {[k]: v}
          : leaves(v)
        );
      })
    );
  }(o))
})(o)

Вторая накапливает значения в массив.

((o) => {
  return o !== Object(o) || Array.isArray(o) ? {}
    : (function () {
      return Object.values((function leaves(o) {
        return [].concat.apply([], !o ? [] : Object.entries(o)
          .map(([k, v]) => {
            return (( !v || typeof v !== 'object'
                || !Object.keys(v).some(k => v.hasOwnProperty(k))
                || (Array.isArray(v) && !v.some(el => typeof el === 'object')))
              ? {[k]: v}
              : leaves(v)
            );
          })
        );
      }(o))).reduce((acc, cur) => {
        return ((key) => {
          acc[key] = !acc[key] ? [cur[key]]
            : new Array(...new Set(acc[key].concat([cur[key]])))
        })(Object.keys(cur)[0]) ? acc : acc
      }, {})
    })(o);
})(o)

Также, пожалуйста, не включайте подобный код в производство, так как его очень сложно отлаживать.

function leaves1(o) {
  return ((o) => {
    return o !== Object(o) || Array.isArray(o) ? {}
      : Object.assign({}, ...function leaves(o) {
      return [].concat.apply([], Object.entries(o)
        .map(([k, v]) => {
          return (( !v || typeof v !== 'object'
              || !Object.keys(v).some(key => v.hasOwnProperty(key))
              || Array.isArray(v))
            ? {[k]: v}
            : leaves(v)
          );
        })
      );
    }(o))
  })(o);
}

function leaves2(o) {
  return ((o) => {
    return o !== Object(o) || Array.isArray(o) ? {}
      : (function () {
        return Object.values((function leaves(o) {
          return [].concat.apply([], !o ? [] : Object.entries(o)
            .map(([k, v]) => {
              return (( !v || typeof v !== 'object'
                  || !Object.keys(v).some(k => v.hasOwnProperty(k))
                  || (Array.isArray(v) && !v.some(el => typeof el === 'object')))
                ? {[k]: v}
                : leaves(v)
              );
            })
          );
        }(o))).reduce((acc, cur) => {
          return ((key) => {
            acc[key] = !acc[key] ? [cur[key]]
              : new Array(...new Set(acc[key].concat([cur[key]])))
          })(Object.keys(cur)[0]) ? acc : acc
        }, {})
      })(o);
  })(o);
}

const obj = {
  l1k0: 'foo',
  l1k1: {
    l2k0: 'bar',
    l2k1: {
      l3k0: {},
      l3k1: null
    },
    l2k2: undefined
  },
  l1k2: 0,
  l2k3: {
    l3k2: true,
    l3k3: {
      l4k0: [1,2,3],
      l4k1: [4,5,'six', {7: 'eight'}],
      l4k2: {
        null: 'test',
        [{}]: 'obj',
        [Array.prototype.map]: Array.prototype.map,
        l5k3: ((o) => (typeof o === 'object'))(this.obj),
      }
    }
  },
  l1k4: '',
  l1k5: new RegExp(/[\s\t]+/g),
  l1k6: function(o) { return o.reduce((a,b) => a+b)},
  false: [],
}
const objs = [null, undefined, {}, [], ['non', 'empty'], 42, /[\s\t]+/g, obj];

objs.forEach(o => {
  console.log(leaves1(o));
});
objs.forEach(o => {
  console.log(leaves2(o));
});

Ответ 5

Это функция, которую я получил в своих общих библиотеках именно для этой цели. Я считаю, что я получил это из аналогичного вопроса stackoverflow, но не могу вспомнить, какой (отредактируйте: Самый быстрый способ сгладить/не сгладить вложенные объекты JSON - спасибо Yoshi!)

function flatten(data) {
    var result = {};
    function recurse (cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
             for(var i=0, l=cur.length; i<l; i++)
                 recurse(cur[i], prop + "[" + i + "]");
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop+"."+p : p);
            }
            if (isEmpty && prop)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
}

Это можно затем вызвать следующим образом:

var myJSON = '{a:2, b:{c:3}}';
var myFlattenedJSON = flatten(myJSON);

Вы также можете добавить эту функцию в стандартный класс строки Javascript следующим образом:

String.prototype.flattenJSON = function() {
    var data = this;
    var result = {};
    function recurse (cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
             for(var i=0, l=cur.length; i<l; i++)
                 recurse(cur[i], prop + "[" + i + "]");
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop+"."+p : p);
            }
            if (isEmpty && prop)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
}

С помощью которого вы можете сделать следующее:

var flattenedJSON = '{a:2, b:{c:3}}'.flattenJSON();

Ответ 6

function flatten(obj: any) {
  return Object.keys(obj).reduce((acc, current) => {
    const key = '${current}';
    const currentValue = obj[current];
    if (Array.isArray(currentValue) || Object(currentValue) === currentValue) {
      Object.assign(acc, flatten(currentValue));
    } else {
      acc[key] = currentValue;
    }
    return acc;
  }, {});
};

let obj = {
  a:2,
  b: {
    c:3
  }
}

console.log(flatten(obj))

Демо https://stackblitz.com/edit/typescript-flatten-json

Ответ 7

Мне нравится этот код, потому что его немного легче понять.

const data = {
  a: "a",
  b: {
    c: "c",
    d: {
      e: "e",
      f: [
        "g",
        {
          i: "i",
          j: {},
          k: []
        }
      ]
    }
  }
};

function flatten(data: any, flatKey = "", response = {}) {
  for (const [key, value] of Object.entries(data)) {
    const newFlatKey = '${flatKey}${key}';
    if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
      flatten(value, '${newFlatKey}.', response);
    } else {
      response[newFlatKey] = value;
    }
  }
  return response;
};

console.log(flatten(data));