简体   繁体   English

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

[英]Best way to flatten JS object (keys and values) to a single depth array

I have written this small function to get all keys and values of an object and store them into an array.我写了这个小 function 来获取 object 的所有键和值并将它们存储到一个数组中。 The object might contain arrays as values... object 可能包含 arrays 作为值...

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

I wonder whether there is a faster/cleaner way to do so:我想知道是否有更快/更清洁的方法:

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
}

I wanted to flatten my deep object to one level depth.我想将我的深层物体展平到一层深度。 None of the above solutions worked for me.以上解决方案都不适合我。

My input:我的输入:

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

Expected output:预期输出:

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

Code that worked for me:对我有用的代码:

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;
}

Flattening Object can be done using recursion as below :扁平对象可以使用递归来完成,如下所示:

Sample Input样本输入

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

Expected Output预期产出

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


My Solution我的解决方案

  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;
}

Hope it helps希望能帮助到你

You could just concat all keys and values.您可以连接所有键和值。 (It does not solve the type casting to number for keys.) (它不能解决键的类型转换为数字的问题。)

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

This answer is an improvement of @Muthukrishnan 's answer这个答案是对@Muthukrishnan 的答案的改进

If you want to flatten an object deeply outputting the values into a one level deep object keyed with the path of the value in the previous object如果要将对象深度展平,则将值输出到以前一个对象中值的路径为键的一级深度对象中

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

Here is how you can effectively do it:以下是如何有效地做到这一点:

 /** * @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 (prefix && 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));

There is an obvious problem that you could encounter with flattening this way if your object contains keys with dots, this is documented in the fiddle如果您的对象包含带点的键,您可能会遇到一个明显的问题,以这种方式展平,这在小提琴中记录

I needed something really simple and here is a one-liner I came up with:我需要一些非常简单的东西,这是我想出的一个单线:

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

Obviously, this is subject to your browser/JS env supporting this syntax.显然,这取决于您的浏览器/JS 环境是否支持这种语法。 Here is a working example.这是一个工作示例。

 const flatten=(obj)=>Object.values(obj).flat() const x={x:[1,2,3],y:[4,5,6,7]} console.log(flatten(x))

If you're feeling really lazy then you can make use of the popular NPM library flat .如果你真的很懒惰,那么你可以使用流行的 NPM 库flat

Example (from their docs)示例(来自他们的文档)

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
// }

Generate an array of tuples (two-element arrays) of keys and values (which might themselves be arrays), then deep-flatten it.生成键和值(它们本身可能是数组)的元组数组(二元素数组),然后对其进行深度扁平化。

 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]}));

You can skip the inner loop if you have to push contents of an array to another array.如果必须将数组的内容推送到另一个数组,则可以跳过内部循环。 See if this helps --看看这是否有帮助——

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);

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

EDIT : This solution is valid only for your scenario where you know that the nesting is till one level only else you need to have some recursion for deep inner objects.编辑:此解决方案仅适用于您知道嵌套直到一层的情况,否则您需要对深层内部对象进行一些递归。

A more modern JavaScript and TypeScript implementation of a simple object to flat property map converter.一个简单的对象到平面属性映射转换器的更现代的 JavaScript 和 TypeScript 实现。 It's using Object.entries to do a proper for of loop only on owned properties.它使用 Object.entries 仅对拥有的属性执行适当的 for of 循环。

Exmaple Input:示例输入:

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

Output:输出:

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

JavaScript: 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);
}

TypeScript:打字稿:

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);
}

I use this recursive function:我使用这个递归函数:

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;
  }, {});
}

Example of use:使用示例:

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

The function below will flatten an object to the specified depth.下面的函数会将对象展平到指定的深度。 This function uses a loop rather than recursion.此函数使用循环而不是递归。 You can choose how child property keys are named, the default is 'parent.child'.您可以选择子属性键的命名方式,默认为“parent.child”。 The result is an array of [key, value] arrays, like Object.entries() .结果是一个[key, value]数组的数组,例如Object.entries() It requires lodash for isPlainObject and partition() , though you could write your own isPlainObject, partition functions if you wanted to remove the dependency.它需要 lodash 用于isPlainObjectpartition() ,但如果您想删除依赖项,您可以编写自己的 isPlainObject 分区函数。

 /** * 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>

This solution can handle deeply nested objects此解决方案可以处理深度嵌套的对象

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 })
}), {});

Remember that this function returns an empty object for strings, dates, numbers, etc.请记住,此函数为字符串、日期、数字等返回一个空对象。

Using Reduce in typescript It will be something like在打字稿中使用 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;
  }, {});

The following solution handles the case for null values and arrays.以下解决方案处理空值和数组的情况。

 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())

I needed something similar that would deep flatten objects recursively into a string, but with customizations on different provided matchers.我需要类似的东西,可以递归地将对象深度扁平化为一个字符串,但对提供的不同匹配器进行自定义。 Based on some examples here this is what I finally ended up with, using lodash and some lodash fp functions.基于这里的一些例子,这就是我最终得到的结果,使用 lodash 和一些 lodash fp 函数。 This way you can reduce true down to just "T" and undefined down to just "U" .通过这种方式,您可以将true减少到仅"T"并将undefined减少到仅"U" The matchers will need a matching key with the process.匹配器将需要一个与进程匹配的密钥。 Everything else is just processed with String(item)其他一切都只是用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("")
}

unit test:单元测试:

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)
})

Using ES6 "Spread Operator"... , Please let me know for flaws使用ES6 "Spread Operator"...,请让我知道缺陷

function flattenObj(data, parent = null){
// Create an empty object .
let dataMap = {}
// Loop over the data object that was given .
for(const key in data){
    // Set a key name by checking if parent was set by previous recursive calls .
    const keyName = parent ? parent + '.' + key : key;
    // Check the data type.
    if(typeof data[key] === 'object' && !Array.isArray(data[key])) {
        // Using ES6 "Spread Operator" i overwrite the dataMap object with:
        // current dataMap + returned object result of the recurive call .
        dataMap = { ...dataMap, ...flattenObj(data[key], keyName)};
    } else {
        // If data type is anything but an object append the "key: value" .
        dataMap[keyName] = data[key];
    }
}
return dataMap;

} }

Here is my TS implementation这是我的 TS 实现

Input:输入:

const object = {
  topLevel: {
    numeric: 0,
    text: "string",
    bool: true,
    nested: {
      notDefined: undefined,
      nulll: null,
      array: [1, 2, 3]
    },
  },
};

const flat = flatten(object);

Expected result:预期结果:

[
  "[topLevel][numeric]=0",
  "[topLevel][text]=string",
  "[topLevel][bool]=true",
  "[topLevel][nested][notDefined]=undefined",
  "[topLevel][nested][nulll]=null",
  "[topLevel][nested][array]=1,2,3",
]

Code:代码:

export type InputValue = string | number | boolean | undefined | null;
export type ParsedValue = InputValue | InputValue[] | ParsedObject;
export type ParsedObject = { [key in string]: ParsedValue };

export const flatten = (object: ParsedObject, parent?: string): string[] => {
  let results: string[] = [];
  for (let key in object) {
    const value = object[key];
    const thisKey = parent ? `${parent}[${key}]` : `[${key}]`;

    results = results.concat(
      isObject(value) ? flatten(value, thisKey) : `${thisKey}=${value}`
    );
  }

  return results;
};

const isObject = (obj: ParsedValue): obj is ParsedObject => {
  return !!obj && typeof obj === "object" && !Array.isArray(obj);
};

And a JS snippet:还有一个 JS 片段:

 const flatten = (object, parent) => { let results = []; for (let key in object) { const value = object[key]; const thisKey = parent? `${parent}[${key}]`: `[${key}]`; results = results.concat( isObject(value)? flatten(value, thisKey): `${thisKey}=${value}` ); } return results; }; const isObject = (obj) => { return.;obj && typeof obj === "object" &&;Array.isArray(obj): }: console,log( flatten({ topLevel: { numeric, 0: text, "string": bool: true, nested: { notDefined, undefined: nulll, null, array, [1, 2; 3] }, }, }) );

I came here because I needed to spread an object as a tree into its property paths (complete paths to leaves in depth-first traversal), but couldn't find it in other answers, so here's my own solution:我来这里是因为我需要将 object 作为树传播到它的属性路径中(深度优先遍历中到叶子的完整路径),但在其他答案中找不到它,所以这是我自己的解决方案:

Test Input测试输入

const obj = {
  x: 1,
  y: "hello",
  z: {
    _id: 5,
    "never.mind": [1, 2, 3],
    a: { "o]k": true, aA: 0 },
    B: null,
    C: [],
    D00d: {},
  },
};

Expected Output (in human readable form)预计 Output(以人类可读的形式)

x : 1
y : "hello"
z ---> _id : 5
z ---> never.mind : [1, 2, 3]
z ---> a ---> o]k : true
z ---> a ---> aA : 0
z ---> B : null
z ---> C : []
z ---> D00d : {}

Code代码

// Just a helper function, you can ignore it.
function areFlatPrimitiveArraysEqual(array1, array2) {
  if (
    !Array.isArray(array1) ||
    !Array.isArray(array2) ||
    array1.length !== array2.length
  ) {
    return false;
  }
  for (const [index, item] of array1.entries()) {
    if (array2[index] !== item) {
      return false;
    }
  }
  return true;
}

// Just a helper function, you can ignore it.
function isLiteralObject(a) {
  return !!a && a.constructor === Object;
}

function SpreadToPaths(obj) {
  // The doIt function is the recursive function that does the main job
  function doIt(obj, out, parentKeyPath) {
    const keys = obj ? Object.keys(obj) : false;
    if (!keys || !isLiteralObject(obj) || keys.length === 0) {
      const outEntry = out.find((entry) =>
        areFlatPrimitiveArraysEqual(entry.keyPath, parentKeyPath)
      );
      outEntry.value = obj;
      return;
    }
    for (let i = 0; i < keys.length; i++) {
      const newKeyPath = [...parentKeyPath, keys[i]];
      // Delete parentEntry because it has an incomplete keyPath.
      const parentEntryIndex = out.findIndex((entry) =>
        areFlatPrimitiveArraysEqual(entry.keyPath, parentKeyPath)
      );
      if (parentEntryIndex !== -1) {
        out.splice(parentEntryIndex, 1);
      }
      // Enter the new parentEntry
      out.push({
        keyPath: newKeyPath,
        value: null,
      });
      doIt(obj[keys[i]], out, newKeyPath);
    }
  }

  // Calling the doIt function with proper initial values to get the result
  const out = [];
  const parentKeyPath = [];
  doIt(obj, out, parentKeyPath);
  return out;
}

const obj = {
  x: 1,
  y: "hello",
  z: {
    _id: 5,
    "never.mind": [1, 2, 3],
    a: { "o]k": true, aA: 0 },
    B: null,
    C: [],
    D00d: {},
  },
};

console.log(SpreadToPaths(obj));

Output Output

[
  { keyPath: [ 'x' ], value: 1 },
  { keyPath: [ 'y' ], value: 'hello' },
  { keyPath: [ 'z', '_id' ], value: 5 },
  { keyPath: [ 'z', 'never.mind' ], value: [ 1, 2, 3 ] },
  { keyPath: [ 'z', 'a', 'o]k' ], value: true },
  { keyPath: [ 'z', 'a', 'aA' ], value: 0 },
  { keyPath: [ 'z', 'B' ], value: null },
  { keyPath: [ 'z', 'C' ], value: [] },
  { keyPath: [ 'z', 'D00d' ], value: {} }
]

Pragmatic Solution 2022务实解决方案 2022

Many beautiful solutions here.这里有许多漂亮的解决方案。 Some I find a little ambitious.有些我觉得有点雄心勃勃。 That's probably because I find recursions great but don't usually understand them straight away.这可能是因为我发现递归很棒,但通常不会立即理解它们。 My personal problem;-) Since you only have one level here, I would use the good old foreach loop.我的个人问题;-) 因为这里只有一个级别,所以我会使用旧的 foreach 循环。

 const nestyObject = { 0: [1,2,3,4], a: ['b','c','d','e'] }; const arr = [] Object.keys(nestyObject).forEach(key => { const left = key; const right = nestyObject[key]; arr.push([left, ...right]); }); console.log(arr)

Eeasier code that worked for me:对我有用的更简单的代码:

getPropertyByString(obj, propString) {
    const props = propString.split('.');
    let nestedObject = {
        ...obj
    };

    props.forEach(prop => {
        nestedObject = nestedObject[prop]
    })

    return nestedObject;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM