Skip to content

Instantly share code, notes, and snippets.

@kirbysayshi
Last active May 14, 2019 15:31
Show Gist options
  • Save kirbysayshi/2ea881ebe643458311f4 to your computer and use it in GitHub Desktop.
Save kirbysayshi/2ea881ebe643458311f4 to your computer and use it in GitHub Desktop.
flatten an object into a single depth using string keys
require('tap-browser-color')();
var test = require('tape');
test('it flattens!', function(t) {
var input = {
users: [
{ name: 'name1', id: 1, image: { '64x64': 'http://1' } },
{ name: 'name2', id: 2, image: { '64x64': 'http://2' } }
],
errors: [ new Error('err1') ],
success: true
}
var expected = {
'users.0.name': 'name1',
'users.0.id': 1,
'users.0.image.64x64': 'http://1',
'users.1.name': 'name2',
'users.1.id': 2,
'users.1.image.64x64': 'http://2',
'users.length': 2,
'errors.0.columnNumber': input.errors[0].columnNumber,
'errors.0.fileName': input.errors[0].fileName,
'errors.0.lineNumber': input.errors[0].lineNumber,
'errors.0.message': input.errors[0].message,
'errors.0.stack': input.errors[0].stack,
'errors.length': 1,
'success': true
}
t.deepEqual(flatten(input), expected);
t.end();
})
function flatten(obj, opt_out, opt_paths) {
var out = opt_out || {};
var paths = opt_paths || [];
return Object.getOwnPropertyNames(obj).reduce(function(out, key) {
paths.push(key);
if (typeof obj[key] === 'object') {
flatten(obj[key], out, paths);
} else {
out[paths.join('.')] = obj[key];
}
paths.pop();
return out;
}, out)
}
@blixt
Copy link

blixt commented Mar 23, 2015

@kirbysayshi: Wouldn't it be neat if there was a distinction between flattened arrays and objects? In case you wanted to unflatten the structure again. Example:

var a = {one: 1, two: [1, 1], three: {'3': 3}};
var b = flatten(o);
// value of b:
{"one": 1, "two[0]": 1, "two[1]": 1, "three.3": 3}

@kirbysayshi
Copy link
Author

I was wondering about that too.. kind of thought you could just ducktype it by checking for .0 and .length.

@kirbysayshi
Copy link
Author

Hmm, I tried adding [] in quick, but you'd have to make the . injection more complicated than .join. So maybe not worth it...

@blixt
Copy link

blixt commented Mar 24, 2015

What about something like this?

var paths = ['a', 'b', 'c', 1, 'd'];
var key = paths.reduce((p, c) => typeof c == 'number' ? p + '[' + c + ']' : (p ? p + '.' : '') + c);
// value of key:
"a.b.c[1].d"

Edit: Actually, Object.keys changes array indices to strings which makes things a bit more complicated :/ I guess you could do this:

var keys = Array.isArray(obj) ? obj.map((_, i) => i) : Object.getOwnPropertyNames(obj);
return keys.reduce(function(out, key) { 

@kirbysayshi
Copy link
Author

Wouldn't you want .length to be present as well?

@dshook
Copy link

dshook commented Oct 7, 2016

Need to change line 41 to be:

    if (typeof obj[key] === 'object' && obj[key]) {

To handle falsey values in the object that otherwise will throw the error
Uncaught TypeError: Cannot convert undefined or null to object

@yangmillstheory
Copy link

yangmillstheory commented Nov 4, 2016

Here's one that doesn't requiring mutating an array. I was asked this in an phone screen today (and bombed the implementation):

const pathDelimiter = '.'

function flatten(source, flattened = {}, keySoFar = '') {
  function getNextKey(key) {
    return `${keySoFar}${keySoFar ? pathDelimiter : ''}${key}`
  }
  if (typeof source === 'object') {
    for (const key in source) {
      flatten(source[key], flattened, getNextKey(key))
    }
  } else {
    flattened[keySoFar] = source
  }
  return flattened
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment