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

前端 未结 10 959
梦毁少年i
梦毁少年i 2020-12-05 05:10

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 {

10条回答
  •  被撕碎了的回忆
    2020-12-05 05:40

    This answer is an improvement of @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 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));

    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

提交回复
热议问题