将JS对象(键和值)展平为单个深度数组的最佳方法

时间:2017-05-23 11:58:07

标签: javascript arrays object

我编写了这个小函数来获取对象的所有键和值并将它们存储到数组中。该对象可能包含数组作为值...

Object { 0: [1,2,3,4] }[0,1,2,3,4]将所有元素转换为整数

我想知道是否有更快/更清洁的方法:

function flattenObject(obj) {
    // Returns array with all keys and values of an object
    var array = [];
    $.each(obj, function (key, value) {
        array.push(key);
        if ($.isArray(value)) {
            $.each(value, function (index, element) {
                array.push(element);
            });
        }
        else {
            array.push(value);
        }
    });

    return array
}

15 个答案:

答案 0 :(得分:14)

我想将深物展平到一个水平深度。以上解决方案均不适用于我。

我的输入

{
    "user": {
        "key_value_map": {
            "CreatedDate": "123424",
            "Department": {
                "Name": "XYZ"
            }
        }
    }
}

预期输出:

{
    "user.key_value_map.CreatedDate": "123424",
    "user.key_value_map.Department.Name": "XYZ"
}

对我有用的代码:

function flattenObject(ob) {
    var toReturn = {};

    for (var i in ob) {
        if (!ob.hasOwnProperty(i)) continue;

        if ((typeof ob[i]) == 'object' && ob[i] !== null) {
            var flatObject = flattenObject(ob[i]);
            for (var x in flatObject) {
                if (!flatObject.hasOwnProperty(x)) continue;

                toReturn[i + '.' + x] = flatObject[x];
            }
        } else {
            toReturn[i] = ob[i];
        }
    }
    return toReturn;
}

答案 1 :(得分:13)

此答案是@Muthukrishnan答案的改进

如果要展平一个对象,则将其值深层输出,将其输出到一个一级深层对象中,该对象用上一个对象中的值的路径作为键

(例如:{ foo: { bar: 'baz'} } => { 'foo.bar': 'baz' }

这是有效的方法:

/**
 * @param ob Object                 The object to flatten
 * @param prefix String (Optional)  The prefix to add before each key, also used for recursion
 **/
function flattenObject(ob, prefix = false, result = null) {
  result = result || {};

  // Preserve empty objects and arrays, they are lost otherwise
  if (typeof ob === 'object' && ob !== null && Object.keys(ob).length === 0) {
    result[prefix] = Array.isArray(ob) ? [] : {};
    return result;
  }

  prefix = prefix ? prefix + '.' : '';

  for (const i in ob) {
    if (Object.prototype.hasOwnProperty.call(ob, i)) {
      if (typeof ob[i] === 'object' && ob[i] !== null) {
        // Recursion on deeper objects
        flattenObject(ob[i], prefix + i, result);
      } else {
        result[prefix + i] = ob[i];
      }
    }
  }
  return result;
}

/**
 * Bonus function to unflatten an object
 *
 * @param ob Object     The object to unflatten
 */
function unflattenObject(ob) {
  const result = {};
  for (const i in ob) {
    if (Object.prototype.hasOwnProperty.call(ob, i)) {
      const keys = i.match(/^\.+[^.]*|[^.]*\.+$|(?:\.{2,}|[^.])+(?:\.+$)?/g); // Just a complicated regex to only match a single dot in the middle of the string
      keys.reduce((r, e, j) => {
        return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 === j ? ob[i] : {}) : []);
      }, result);
    }
  }
  return result;
}


// TESTS
const obj = {
  value: {
    foo: {
      bar: 'yes',
      so: {
        freakin: {
          nested: 'Wow',
        }
      }
    },
  },
  // Some edge cases to test
  test: [true, false, [null, undefined, 1]],
  not_lost: [], // Empty arrays should be preserved
  not_lost2: {}, // Empty objects should be preserved
  // Be careful with object having dots in the keys
  'I.like.dots..in.object.keys...': "... Please don't override me",
  I: {
    like: {
      'dots..in': {
        object: {
          'keys...': "You've been overwritten"
        }
      }
    }
  }
};
console.log(flattenObject(['I', {'am': 'an array'}]));
let flat = flattenObject(obj);
console.log(flat, unflattenObject(flat));

如果对象包含带点的键,则用这种方法展平会遇到一个明显的问题,该问题记录在小提琴中

答案 2 :(得分:10)

您可以将所有键和值连接起来。 (它不能解决类型转换为键的数字。)

var object =  { 0: [1, 2, 3, 4] },
    result = Object.keys(object).reduce(function (r, k) {
        return r.concat(k, object[k]);
    }, []);
    
console.log(result);

答案 3 :(得分:3)

简单对象到平面属性映射转换器的更现代的 JavaScript 和 TypeScript 实现。它使用 Object.entries 仅对拥有的属性执行适当的 for of 循环。

示例输入:

const address = {
  name: 'Address 1',
  address: {
    street: {name: 'Test Street', no: 123}
  }
};

输出:

{
    'address.street.name': 'Test Street'
    'address.street.no': 123
    'name': 'Address 1'
}

JavaScript:

export function toFlatPropertyMap(obj, keySeparator = '.') {
  const flattenRecursive = (obj, parentProperty, propertyMap = {}) => {
    for(const [key, value] of Object.entries(obj)){
      const property = parentProperty ? `${parentProperty}${keySeparator}${key}` : key;
      if(value && typeof value === 'object'){
        flattenRecursive(value, property, propertyMap);
      } else {
        propertyMap[property] = value;
      }
    }
    return propertyMap;
  };
  return flattenRecursive(obj);
}

打字稿:

export function toFlatPropertyMap(obj: object, keySeparator = '.') {
  const flattenRecursive = (obj: object, parentProperty?: string, propertyMap: Record<string, unknown> = {}) => {
    for(const [key, value] of Object.entries(obj)){
      const property = parentProperty ? `${parentProperty}${keySeparator}${key}` : key;
      if(value && typeof value === 'object'){
        flattenRecursive(value, property, propertyMap);
      } else {
        propertyMap[property] = value;
      }
    }
    return propertyMap;
  };
  return flattenRecursive(obj);
}

答案 4 :(得分:2)

生成一个由元组和值组成的元组(两元素数组)(可能本身就是数组),然后对其进行深度展平。

function flattenObject(obj) { 
      return flatten(Object.keys(obj).map(k => [toNumber(k), obj[k]]));
}

// Substitute your own favorite flattening algorithm.
const flatten = a => Array.isArray(a) ? [].concat(...a.map(flatten)) : a;

// Convert to number, if you can.
const toNumber = n => isNaN(+n) ? n : +n;

console.log(flattenObject({a: [1, 2], b: 3, 0: [1, 2, 3, 4, 5]}));

答案 5 :(得分:2)

如果必须将数组的内容推送到另一个数组,则可以跳过内部循环。看看这是否有帮助 -

function flattenObject(obj) {
// Returns array with all keys and values of an object
var array = [];
$.each(obj, function (key, value) {
    array.push(key);
    if ($.isArray(value)) {
        Array.prototype.push.apply(array, value);
    }
    else {
        array.push(value);
    }
});

return array;
}
var obj = {"key1" : [1,3,3],"key2" : "val", "key3":23};
var output = flattenObject(obj);
console.log(output);

小提琴链接 - https://jsfiddle.net/0wu5z79a/1/

编辑:此解决方案仅对您知道嵌套直到一个级别的情况有效,否则您需要对深内部对象进行一些递归。

答案 6 :(得分:2)

平化对象可以使用以下递归来完成:

样本输入

  let obj = {
    name: "test",
    address: {
    personal: "abc", 
    office: {
       building : 'random'
       street : 'some street',
    }
    }
}

预期产量

{
    name : "test",
    address_personal: "abc"
    address_office_building: "random"
    address_office_street: "some street"
}


我的解决方案

  function flattenObj(obj, parent, res = {}){
    for(let key in obj){
        let propName = parent ? parent + '_' + key : key;
        if(typeof obj[key] == 'object'){
            flattenObj(obj[key], propName, res);
        } else {
            res[propName] = obj[key];
        }
    }
    return res;
}

希望有帮助

答案 7 :(得分:2)

我使用这个递归函数:

function flattenObject(obj, prefix = '') {
  return Object.keys(obj).reduce((acc, k) => {
    const pre = prefix.length ? prefix + '.' : '';
    if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
    else acc[pre + k] = obj[k];
    return acc;
  }, {});
}

使用示例:

const obj = { a: { b: { c: 1 } }, d: 1 };
const output = flattenObject(obj);
console.log(output); //{"a.b.c":1,"d":1}

答案 8 :(得分:1)

如果您真的很懒,可以使用流行的NPM库flat

示例(来自他们的文档)

var flatten = require('flat')

flatten({
    key1: {
        keyA: 'valueI'
    },
    key2: {
        keyB: 'valueII'
    },
    key3: { a: { b: { c: 2 } } }
})

// {
//   'key1.keyA': 'valueI',
//   'key2.keyB': 'valueII',
//   'key3.a.b.c': 2
// }

答案 9 :(得分:0)

下面的函数会将对象展平到指定的深度。此函数使用循环而不是递归。您可以选择子属性键的命名方式,默认值为“ parent.child”。结果是[key, value]个数组的数组,例如Object.entries()。尽管您可以编写自己的isPlainObject分区函数(如果要删除依赖项),但它需要isPlainObjectpartition()的lodash。

/**
 * Returns an array containing the properties of the given Object in the same format
 * as Object.entries(). Goes through child objects to the specified depth, 
 * flattening the properties and prefixing child keys with a parent key names.
 * @param {Object} object to retrieve property values for
 * @param {Number} maxDepth the maximum number of times to look at properties of
 * properties of the given object.
 * Set to 1 to only retrieve the property values of the given object, 2 to get
 * properties and sub-properties etc.
 * @param {Function} keyPrefixer a function that takes a parent object name, and
 * a child object name and returns a string representing the combined name.
 * @returns {Array} containing the properties and child properties of the given object.
 * Each property is returned as an array [key, value]. 
 * Returns an empty array if object is null, undefined, not-an-object, or empty.
 */
const flattenEntries = (
  object,
  maxDepth = 2,
  keyPrefixer = (parentKey, childKey) => `${parentKey}.${childKey}`) => {

  if (!object || !_.isPlainObject(object)) {
    return [];
  }

  // make maxDepth >= 1
  maxDepth = Math.max(1, Math.abs(maxDepth));

  const entryIsNotAnObject = ([key, val]) => !_.isPlainObject(val);

  let [simpleProperties, childObjects] = _.partition(Object.entries(object), entryIsNotAnObject);

  let result = simpleProperties;

  for (let depth = 1; depth < maxDepth; depth++) {

    for (let [childObjectKey, childObject] of childObjects) {
      const entries = Object.entries(childObject);
      const addParentPrefixToKey = ([key, val]) => [keyPrefixer(childObjectKey, key), val];
      const prefixedEntries = entries.map(addParentPrefixToKey);
      [simpleProperties, childObjects] = _.partition(prefixedEntries, entryIsNotAnObject);
      result = result.concat(simpleProperties);
    }
  }

  return result;
};

const test = {
  a: 'one',
  b: {
    c: 'three',
    d: {
      e: {
        f: ['six', 'six'],
        g: 7
      }
    }
  }
};

console.log(flattenEntries(test, 10));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

答案 10 :(得分:0)

此解决方案可以处理深度嵌套的对象

const isObject = o => o && typeof o === 'object' && !(o instanceof Date);

const flattenObject = obj => Object.entries(obj).reduce((acc, [key, val]) => ({
  ...acc, ...(isObject(val) ? flattenObject(val) : { [key]: val })
}), {});

请记住,此函数将为字符串,日期,数字等返回空对象。

答案 11 :(得分:0)

在打字稿中使用 Reduce 就像

export const flattenObject = (obj: Record<string, unknown>): Record<string, unknown> =>
  Object.entries(obj).reduce((acc, [key, value]) => {
    if (typeof value === 'object' && value !== null) {
      Object.entries(value).forEach(([iKey, iValue]) => {
        acc[`${key}-${iKey}`] = iValue;
      });
    } else {
      acc[key] = value;
    }
    return acc;
  }, {});

答案 12 :(得分:0)

以下解决方案处理空值和数组的情况。

let user = {
  name: "John Doe",
  address: {
    personal: {
      city: "Haridwar",
      state: "Uttrakhand",
      area: "Majra",
    },
    office: {
      city: "Hyderabad",
      area: {
        landmark: "Hi Tech",
        pincode: [12321, 23414],
        lane: null
      }
    }
  }
}


function more_magic() {
  let ans = {};
  let magic = function (obj, parent) {
    for (let key in obj) {
      if (typeof obj[key] === "object" && obj[key] !== null && Array.isArray(obj[key]) === false) {
        magic(obj[key], parent + "_" + key);
      } else {
        ans[parent + "_" + key] = obj[key];
      }
    }
  }
  magic(user, "user");
  return ans;
}
console.log(more_magic())

答案 13 :(得分:0)

我需要类似的东西,它可以递归地将对象深度扁平化为一个字符串,但对提供的不同匹配器进行自定义。基于这里的一些例子,这就是我最终得到的结果,使用 lodash 和一些 lodash fp 函数。通过这种方式,您可以将 true 减少到仅 "T",将 undefined 减少到仅 "U"。匹配器将需要一个与进程匹配的密钥。其他一切都用 String(item)

处理
import _ from "lodash"

import flow from "lodash/fp/flow"
import some from "lodash/fp/some"
import reduce from "lodash/fp/reduce" 
....

const deepValuesToComparableString = items => {
  let matchers = {
    u: _.isUndefined, n: _.isNull,
    b: _.isBoolean, o: _.isObject,
    a: _.isArray, z: _.stubTrue
  }
  let process = {
    u: _.constant("U"), n: _.constant("N"),
    b: b => b ? "T" : "F", o: flow(_.flatMapDeep, _.values),
    a: _.flattenDeep, z: String
   }
  let convertForMatch = _.cond(_.zip(_.values(matchers), _.values(process)))
  let stillHasDepth = some(matchers.o || matchers.a)
  let valuesFor = reduce((acc, item) => [...acc, ...convertForMatch(item)], [])
  let flatReduceValues = reduce((acc, item) => [
    ...acc, ...stillHasDepth(item) 
      ? valuesFor(flatReduceValues(valuesFor(item))) 
      : valuesFor(item)
  ], [])
  return flatReduceValues(items).join("")
}

单元测试:

test("it converts a 1d array of models into a string", () => {
  let someArrayData = [
    new TestDataClass({ someStr: "Test1", someOtherStr: "Abc", someNum: 1, someBool: false, someObjWithArrField: { someField: "some obj field", subRows: [{someSubRowField: "123", someOtherSubRowField: "testA"  }]}}),
    new TestDataClass({ someStr: "Test2", someOtherStr: undefined, someNum: 2, someBool: true,  someObjWithArrField: { someField: "obj field 2", subRows: [{someSubRowField: "234", someOtherSubRowField: "test B" }]}}),
    new TestDataClass({ someStr: "Sfds3", someOtherStr: "GGG", someNum: 3, someBool: null,  someObjWithArrField: { someField: "some field 3", subRows: [{someSubRowField: "456", someOtherSubRowField: "test C" }]}}),
  ]
  let result = deepValuesToComparableString(someArrayData)
  let expectedStr = "Test1Abc1Fsome obj field123testATest2U2Tobj field 2234test BSfds3GGG3Nsome field 3456test C"
  expect(result).toEqual(expectedStr)
})

答案 14 :(得分:-1)

我需要一些非常简单的东西,这是我想出的一种方法:

function flatten(obj){
  return Object.values(obj).flat()
}

const flatten=(obj)=>Object.values(obj).flat()

const=nt x={x:[1,2,3],y:[4,5,6,7]}

console.log(flatten(x))