31

Sorry I don't know how to phrase the question title. Please help edit if possible.

I have an object like this:

{
    a: 'jack',
    b: {
        c: 'sparrow',
        d: {
           e: 'hahaha'
        }
    }
}

I want to make it look like:

{
    'a': 'jack',
    'b.c': 'sparrow',
    'b.d.e': 'hahaha'
}

// so that I can use it this way:
a['b.d.e']

jQuery is ok too. I know for the nested object, I can use a.b.d.e to get hahaha, but today I have to use it like a['b.d.e'] -_-!!! How can I achieve this? Thanks in advance :)

shrekuu
  • 1,716
  • 1
  • 23
  • 38

15 Answers15

39

You could use a recursive function to crawl the object and flatten it for you.

var test = {
    a: 'jack',
    b: {
        c: 'sparrow',
        d: {
            e: 'hahaha'
        }
    }
};

function traverseAndFlatten(currentNode, target, flattenedKey) {
    for (var key in currentNode) {
        if (currentNode.hasOwnProperty(key)) {
            var newKey;
            if (flattenedKey === undefined) {
                newKey = key;
            } else {
                newKey = flattenedKey + '.' + key;
            }

            var value = currentNode[key];
            if (typeof value === "object") {
                traverseAndFlatten(value, target, newKey);
            } else {
                target[newKey] = value;
            }
        }
    }
}

function flatten(obj) {
    var flattenedObject = {};
    traverseAndFlatten(obj, flattenedObject);
    return flattenedObject;
}

var flattened = JSON.stringify(flatten(test));
console.log(flattened);

One way to reverse this, if needed, is a nested set of loops. There is probably a cleaner way to accomplish this though:

var test = {'a':'jack','b.c':'sparrow','b.d.e':'hahaha'};

function expand(target, keySeparator) {
    var result = {};
    for (var key in target) {
        if (target.hasOwnProperty(key)) {
          var nestedKeys = key.split(keySeparator);
          // Get the last subKey
          var leaf = nestedKeys[nestedKeys.length - 1];
          // Get all subKeys except for the last
          var branch = nestedKeys.slice(0, nestedKeys.length - 1);
          
          var currentTarget = result;
          for (var i = 0; i < branch.length; i += 1) {
            var subKey = nestedKeys[i];
            // If this is the first time visiting this branch, we need to instantiate it
            if (currentTarget[subKey] === undefined) {
              currentTarget[subKey] = {};
            }
            // Visit the branch
            currentTarget = currentTarget[subKey];
          }
          currentTarget[leaf] = target[key];
        }
    }
    return result;
}

var expanded = JSON.stringify(expand(test, "."));
console.log(expanded);
Marie
  • 2,114
  • 1
  • 17
  • 31
17

An alternative recursive implementation. I just felt like writing one implementation myself, even though the current ones are already really good.

The recursive function checks whether the key is of type 'object'.

  • If it's an object, we iterate by each object's key.
  • Else, we add it into our result object.
function flat(res, key, val, pre = '') {
  const prefix = [pre, key].filter(v => v).join('.');
  return typeof val === 'object'
    ? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
    : Object.assign(res, { [prefix]: val});
}
return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});

Flat NPM package

Or you can simply use flat npm package, which is a well known tested library.

var flatten = require('flat')
flatten(obj);

⬑ I would use this in serious code.

[Extra] Neater call to the function above

function flatObject(input) {
  function flat(res, key, val, pre = '') {
    const prefix = [pre, key].filter(v => v).join('.');
    return typeof val === 'object'
      ? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
      : Object.assign(res, { [prefix]: val});
  }

  return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});
}

const result = flatObject(input);

[Extra] Demo

http://codepen.io/zurfyx/pen/VpErja?editors=1010

function flatObject(input) {
  function flat(res, key, val, pre = '') {
    const prefix = [pre, key].filter(v => v).join('.');
    return typeof val === 'object'
      ? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res)
      : Object.assign(res, { [prefix]: val});
  }

  return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {});
}

const result = flatObject({
    a: 'jack',
    b: {
        c: 'sparrow',
        d: {
           e: 'hahaha'
        }
    }
});

document.getElementById('code').innerHTML = JSON.stringify(result, null, 2);
<pre><code id="code"></code></pre>
zurfyx
  • 31,043
  • 20
  • 111
  • 145
  • Slight tweak to define using functional notation and add check if the input was a JSON string instead of an object. ```const flatten = (input) => { function flat(res, key, val, pre = '') { const prefix = [pre, key].filter(v => v).join('_'); return typeof val === 'object' ? Object.keys(val).reduce((prev, curr) => flat(prev, curr, val[curr], prefix), res) : Object.assign(res, { [prefix]: val}); } if (typeof input === 'string') { input = JSON.parse(input); } return Object.keys(input).reduce((prev, curr) => flat(prev, curr, input[curr]), {}); }``` – Shanerk Sep 05 '22 at 19:44
  • for future readers, I would not recommend using this function. it does not handle input of: strings, numbers, booleans, undefined, null, Dates; it does not handle leaves of: Dates, other class objects. – Patrick Michaelsen Oct 13 '22 at 20:50
  • this handles those cases and includes tests: https://github.com/prmichaelsen/parm/blob/e4b5eaadc03869d9e73a51759f459a8730f17f7e/libs/util/src/lib/flatten.ts and https://github.com/prmichaelsen/parm/blob/e4b5eaadc03869d9e73a51759f459a8730f17f7e/libs/util/src/lib/flatten.spec.ts – Patrick Michaelsen Oct 13 '22 at 23:13
14

You could loop through the entries of the object. If the value is an object, recursively call the function. Use flatMap to get a flattened array of entries.

Then use Object.fromEntries() to get an object from the flattened array of entries

const input = {
  a: 'jack',
  b: {
    c: 'sparrow',
    d: {
      e: 'hahaha'
    }
  }
}

const getEntries = (o, prefix = '') => 
  Object.entries(o).flatMap(([k, v]) => 
    Object(v) === v  ? getEntries(v, `${prefix}${k}.`) : [ [`${prefix}${k}`, v] ]
  )

console.log(
  Object.fromEntries(getEntries(input))
)

Note: Object(v) === v returns true only for objects. typeof v === 'object' is true for v = null too.

adiga
  • 34,372
  • 9
  • 61
  • 83
  • Just a note that this otherwise great answer considers Date to be objects, while Date should be probably treated like a value in this scenario. – Kuba Wyrostek Jan 28 '22 at 00:26
6

Recursive is the best solution for this case.

function flatten(input, reference, output) {
  output = output || {};
  for (var key in input) {
    var value = input[key];
    key = reference ? reference + '.' + key : key;
    if (typeof value === 'object' && value !== null) {
      flatten(value, key, output);
    } else {
      output[key] = value;
    }
  }
  return output;
}
var result = flatten({
  a: 'jack',
  b: {
    c: 'sparrow',
    d: {
      e: 'hahaha'
    }
  }
});
document.body.textContent = JSON.stringify(result);
Lewis
  • 14,132
  • 12
  • 66
  • 87
4

Option 1: export a flat object with just the Leaves. i.e object exported contains just paths with primitive value at the end ( see example ) .

//recursion: walk on each route until the primitive value.
//Did we found a primitive?
//Good, then join all keys in the current path and save it on the export object.
export function flatObject(obj) {
    const flatObject = {};
    const path = []; // current path

    function dig(obj) {
        if (obj !== Object(obj))
            /*is primitive, end of path*/
            return flatObject[path.join('.')] = obj; /*<- value*/ 
    
        //no? so this is an object with keys. go deeper on each key down
        for (let key in obj) {
            path.push(key);
            dig(obj[key]);
            path.pop();
        }
    }

    dig(obj);
    return flatObject;
}

Example

let  obj = {aaa:{bbb:{c:1,d:7}}, bb:{vv:2}}
console.log(flatObject(obj))
/*
{
  "aaa.bbb.c": 1,
  "aaa.bbb.d": 7,
  "bb.vv": 2
}
*/

Option 2: export a flat object with all intermidate paths. a little bit shorter and simpler (see example).

export function flatObject(obj) {
    const flatObject = {};
    const path = []; // current path

    function dig(obj) {
        for (let key in obj) {
            path.push(key);
            flatObject[path.join('.')] = obj[key];
            dig(obj[key])
            path.pop();
        }
    }

    dig(obj);
    return flatObject;
}

Example:

let  obj = {aaa:{bbb:{c:1,d:7}}, bb:{vv:2}}
console.log(flatObject(obj))
/*{
  "aaa": {
    "bbb": {
      "c": 1,
      "d": 7
    }
  },
  "aaa.bbb": {
    "c": 1,
    "d": 7
  },
  "aaa.bbb.c": 1,
  "aaa.bbb.d": 7,
  "bb": {
    "vv": 2
  },
  "bb.vv": 2
}
*/
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
pery mimon
  • 7,713
  • 6
  • 52
  • 57
3

A recursive approach by using a parameter for parent keys.

const
    getValues = (object, parents = []) => Object.assign({}, ...Object
        .entries(object)
        .map(([k, v]) => v && typeof v === 'object'
            ? getValues(v, [...parents, k])
            : { [[...parents, k].join('.')]: v }
        )
    ),
    object = { a: 'jack', b: { c: 'sparrow', d: { e: 'hahaha' } } };

console.log(getValues(object));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
2

Another approach using ES6.

const obj = {
  a: "jack",
  b: {
    c: "sparrow",
    d: {
      e: "hahaha"
    }
  }
};

function flattenObj(value, currentKey) {
  let result = {};

  Object.keys(value).forEach(key => {

    const tempKey = currentKey ? `${currentKey}.${key}` : key;

    if (typeof value[key] !== "object") {
      result[tempKey] = value[key];
    } else {
      result = { ...result, ...flattenObj(value[key], tempKey) };
    }
  });

  return result;
}

console.log(flattenObj(obj));
Bhaskar Gyan Vardhan
  • 9,057
  • 2
  • 18
  • 18
1

Just an example how can you achieve that with ES6 features.

const flatObject = obj => {
    const keys = Object.keys(obj)

    return keys.reduce((acc, k) => {
        const value = obj[k]

        return typeof value === 'object' ?
             {...acc, ...ObjectUtils.flatObject(value)} :
             {...acc, [k]: value}
    } , {})
}
Bruno Joaquim
  • 1,423
  • 1
  • 14
  • 15
1

Here is a simple solution -

function flatObj(obj, newObj, parentKey) {
    for(let key in obj) {

        const currKey = parentKey.length > 0 ? `${parentKey}.${key}` : key

        if (typeof obj[key] === "object") {
            flatObj(obj[key], newObj, currKey);
        } else {
            newObj[currKey] = obj[key];
        }
    }

    return newObj;
};

    let obj = {
       a: 'jack',
       b: {
          c: 'sparrow',
          d: {
             e: 'hahaha'
          }
       }
    };

console.log(flatObj(obj, {}, ""));
0

var flattenObject = function(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];
    }
  }
  console.log(toReturn)
  return toReturn;
};

var ob = {
  'a': {
    'b': {
      'b2': 2
    },
    'c': {
      'c2': 2,
      'c3': 3
    }
  }
};
flattenObject(ob);
Manas Sahu
  • 779
  • 8
  • 8
0
const flatten = function(obj) {
  const result = {};

  for (let key in obj) {
    if (typeof obj[key] === 'object') {
      const childObj = flatten(obj[key]);
    
      for (let childObjKey in childObj) {
        result[`${key}.${childObjKey}`] = childObj[childObjKey];
      }
    } else {
      result[key] = obj[key];
    }
  }
  return result
}

var test = {
    a: 'jack',
    b: {
        c: 'sparrow',
        d: {
            e: 'hahaha'
        }
    }
};

console.log(flatten(test));
Constantin
  • 3,655
  • 1
  • 14
  • 23
0

If you are here to convert nested object like this

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

to this

  {
  a:2,
  c:3
}

than try this Object.assign one line to flat the nested object

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

reference to this post

https://stackoverflow.com/a/33037683/8609844

saud00
  • 465
  • 1
  • 4
  • 13
0

function flatObject(object, currentKey) {
  return Object.entries(object).reduce((acc, [key, value]) => {
    const tempKey = currentKey ? `${currentKey}.${key}` : key;

    return value instanceof Object ?
      { ...acc, ...flatObject(value, tempKey) } :
      { ...acc, [tempKey]: value };
  }, {});
}

const test = {
    a: 'jack',
    b: {
        c: 'sparrow',
        d: {
            e: 'hahaha'
        }
    }
};

console.log(flatObject(test))
0

I think this is the simplest solution of all the above, I used a recursive function to track the key and finally return it with a new object.

let obj = {
    a: 'jack',
    b: {
        c: 'sparrow',
        d: {
           e: 'hahaha'
        }
    }
}

const flatObj = (obj) => {
    let obj1 = {}
    function recur(obj, st){
      for(let ob in obj){
        if(typeof obj[ob] === 'object' && obj[ob] !== null){
          recur(obj[ob], st+ob)
        }else{
          obj1[(st+ob).split('').join('.')] = obj[ob]
        }
      }
   }

  recur(obj, "")
  
  return obj1
}

let res = flatObj(obj)

console.log(res)

Aaquib
  • 404
  • 8
  • 17
-4

Try this

const result = [].concat.apply([], parentArray.map((item: any) => item.any)

  • 2
    This isn’t JavaScript, this isn’t about plain objects, and it doesn’t even work for Arrays. There’s no context how this code is supposed to work. And in general, [“try this” answers are not useful](//meta.stackoverflow.com/a/298811/4642212). Please add some explanation. Code-only answers are less useful for future readers and don’t explain the OP’s mistake or how to approach the problem. – Sebastian Simon Oct 15 '21 at 04:17