Best way to flatten JS object (keys and values) to

2020-02-10 19:23发布

问题:

I have written this small function to get all keys and values of an object and store them into an array. The object might contain arrays as values...

Object { 0: [1,2,3,4] } to [0,1,2,3,4] converting all elements to integers

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
}

回答1:

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



回答2:

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


回答3:

Flattening Object can be done using recursion as below :

Sample Input

  let 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



回答4:

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



回答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/

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.



回答6:

If you're feeling really lazy then you can make use of the popular NPM library 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
// }


回答7:

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



回答8:

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'. The result is an array of [key, value] arrays, like 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.

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



回答9:

This answer is an improvement over @Muthukrishnan 's answer

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

Here is how you can effectively do it:

/**
 * @param Object ob      The object to flatten
 * @param String prefix  (Optional) The prefix to add before each key, also used for recursion
 **/
function flattenObject(ob, prefix) {
  const toReturn = {};
  prefix = prefix ? prefix + '.' : '';

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

    if (typeof ob[i] === 'object' && ob[i] !== null) {
      // Recursion on deeper objects
      Object.assign(toReturn, flattenObject(ob[i], prefix + i));
    } else {
      toReturn[prefix + i] = ob[i];
    }
  }
  return toReturn;
}


const obj = {
  value: {
    foo: {
      bar: 'yes',
      so: {
        freakin: {
          nested: 'Wow',
        }
      }
    },
  },
  // Some edge cases to test
  test: [true, false, [null, undefined, 1]],
  // 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(obj));

There is an obvious problem that you could encounter with flattening this way if your object contains keys with dots documented in the fiddle