-
-
Save penguinboy/762197 to your computer and use it in GitHub Desktop.
var flattenObject = function(ob) { | |
var toReturn = {}; | |
for (var i in ob) { | |
if (!ob.hasOwnProperty(i)) continue; | |
if ((typeof ob[i]) == 'object') { | |
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; | |
}; |
this version will return a flattened object which can be used to generate rails style params which will be interpreted to a ruby object. example output goes {a: {b: 'b', x: { y: 'y'}}, c: 'c', d: { e: 'e'}} => { 'a[b]': 'b', 'a[x][y]': 'y', 'c': 'c', 'd[e]': 'e' }. this can be useful if you would like to use rails as an api server from javascript / ajax.
// ussage :
// flattenobj( object ); // 'key' and 'q' below are internal parameters only. do not use.
function flattenobj(obj, key = '', q = {}) {
for (k in obj) {
if (typeof (obj) === "object") {
let kb;
key === '' ? kb = k : kb = '[' + k + ']';
flattenobj(obj[k], key + kb, q);
} else {
q[key] = obj;
}
}
return q;
}
TypeScript version with custom keys:
type KeyFactory = (previousKey: string, currentKey: string) => string;
function flattenObject(ob: { [key: string]: any }, keyFactory: KeyFactory | null = null): { [key: string]: any } {
if (keyFactory === null) {
keyFactory = (previousKey, currentKey) => previousKey + '.' + currentKey;
}
const toReturn: { [key: string]: string } = {};
for (const i in ob) {
if (!ob.hasOwnProperty(i)) {
continue;
}
if ((typeof ob[i]) === 'object') {
const flatObject = flattenObject(ob[i]);
for (const x in flatObject) {
if (!flatObject.hasOwnProperty(x)) {
continue;
}
toReturn[keyFactory(i, x)] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
}
Usage:
const obj = { foo: { bar: 'baz' } };
flattenObject(obj, (previous, current) => `${previous}[${current}]`)
// Returns { 'foo[bar]': 'baz' }
What is the lasted version? 🤔
last version
const flatten = (object, prefix = '') => {
return Object.keys(object).reduce((prev, element) => {
return object[element] && typeof object[element] == 'object' && !Array.isArray(element)
? { ...prev, ...flatten(object[element], `${prefix}${element}.`) }
: { ...prev, ...{ [`${prefix}${element}`]: object[element] } }
}, {})
}
Usage
const obj = { foo: { bar: 'baz' } };
flatten(obj)
// Return {foo.bar: "baz"}
in node version 9.x i got the error:
? { ...prev, ...flatten(object[element], `${prefix}${element}.`) }
^^^
SyntaxError: Unexpected token ...
@tonioriol and others any body knows how to transform back the flattened object to the original object. Lets say i change the values of the 'x.y.z' keys in the flattened object, now i want to get the original object structure with the new values in them.
I used @ruisebastiao and fixed few minor things
const flatten = (object, prefix = '') =>
Object.keys(object).reduce(
(prev, element) =>
object[element] &&
typeof object[element] === 'object' &&
!Array.isArray(object[element])
? { ...prev, ...flatten(object[element], `${prefix}${element}.`) }
: { ...prev, ...{ [`${prefix}${element}`]: object[element] } },
{},
);
I used @arnigudj and added nested array flattening:
const flatten = (objectOrArray, prefix = '') => {
const nestElement = (prev, value, key) => (value
&& typeof value === 'object'
? { ...prev, ...flatten(value, `${prefix}${key}.`) }
: { ...prev, ...{ [`${prefix}${key}`]: value } });
return Array.isArray(objectOrArray)
? objectOrArray.reduce(nestElement, {})
: Object.keys(objectOrArray).reduce(
(prev, element) => nestElement(prev, objectOrArray[element], element),
{},
);
};
Thank you!
anyone can revert this, i want to create object from single-depth object
I used @pozylon and added a formatter function, which will be applied on each key.
const flatten = (objectOrArray, prefix = '', formatter = (k) => ('.' + k)) => {
const nestElement = (prev, value, key) => (
(value && typeof value === 'object')
? { ...prev, ...flatten(value, `${prefix}${formatter(key)}`, formatter) }
: { ...prev, ...{ [`${prefix}${formatter(key)}`]: value } });
return Array.isArray(objectOrArray)
? objectOrArray.reduce(nestElement, {})
: Object.keys(objectOrArray).reduce(
(prev, element) => nestElement(prev, objectOrArray[element], element),
{},
);
};
Same thing as @mehrjoo, simply allows the possibility to have different formatter for the level 0 keys of your object
const flatten = (objectOrArray, prefix = '', formatter = (k) => (k)) => {
const nestedFormatter = (k) => ('.' + k)
const nestElement = (prev, value, key) => (
(value && typeof value === 'object')
? { ...prev, ...flatten(value, `${prefix}${formatter(key)}`, nestedFormatter) }
: { ...prev, ...{ [`${prefix}${formatter(key)}`]: value } });
return Array.isArray(objectOrArray)
? objectOrArray.reduce(nestElement, {})
: Object.keys(objectOrArray).reduce(
(prev, element) => nestElement(prev, objectOrArray[element], element),
{},
);
};
This way for :
input = { 'a':{ 'b':{ 'b2':2 }, 'c':{ 'c2':2, 'c3':3 } } }
You have this result :
{ a.b.b2: 2, a.c.c2: 2, a.c.c3: 3 }
While with @mehrjoo you have this :
{ .a.b.b2: 2, .a.c.c2: 2, .a.c.c3: 3 }
very nicely done! Thank you for this!
anyone can revert this, i want to create object from single-depth object
@edwardEvans094 I found code to reverse this at https://stackoverflow.com/questions/42694980/how-to-unflatten-a-javascript-object-in-a-daisy-chain-dot-notation-into-an-objec
Really useful, thank you!
anybody has an opposite function? to unflatten it?
A Typescript 3.7+ version:
export function flatten<T extends Record<string, any>>(
object: T,
path: string | null = null,
separator = '.'
): T {
return Object.keys(object).reduce((acc: T, key: string): T => {
const newPath = [path, key].filter(Boolean).join(separator);
return typeof object?.[key] === 'object'
? { ...acc, ...flatten(object[key], newPath, separator) }
: { ...acc, [newPath]: object[key] };
}, {} as T);
}
@guillim
How do I remove the numbers from the keys in an array of objects? I have this fiddle. This data:
var data = {
"ticker": "AAPL",
"name": "Apple",
"data": [
1,2,3,4
],
"obj": [
{"revenue": 1, "income": 11},
{"revenue": 2, "income": 22},
],
}
Results in:
["ticker", "name", "data.0", "data.1", "data.2", "data.3", "obj.0.revenue", "obj.0.income", "obj.1.revenue", "obj.1.income"]
I'd like:
["ticker", "name", "data", "data", "data", "data", "obj.revenue", "obj.income", "obj.revenue", "obj.income"]
What an awesome thread. Thanks to everyone here!
I made an improvement from @x47188 version to handle null
values and empty []
arrays. Also added the Date
and Regex
object for the hell of it.
export function flatten<T extends Record<string, any>>(
object: T,
path: string | null = null,
separator = '.'
): T {
return Object.keys(object).reduce((acc: T, key: string): T => {
const value = object[key];
const newPath = [path, key].filter(Boolean).join(separator);
const isObject = [
typeof value === 'object',
value !== null,
!(value instanceof Date),
!(value instanceof RegExp),
!(Array.isArray(value) && value.length === 0),
].every(Boolean);
return isObject
? { ...acc, ...flatten(value, newPath, separator) }
: { ...acc, [newPath]: value };
}, {} as T);
}
I'm wondering how to do it similarly to what @gitty-git-git asks.
But i want to keep arrays as they are unless they have objects, in that case flatten the objects.
For example this:
flatten({a:{b:2},c:[{e:{f:4}}]})
{
"a.b": 2,
"c": [
{
"e.f": 4
}
]
}
Anyone has an idea? @gitty-git-git did you find a way to do yours?
slighlty modified from @codeBelt to output array indices as [0]
instead of as property .0
export function flatten<T extends Record<string, any>>(object: T, path: string | null = null, separator = '.'): T {
return Object.keys(object).reduce((acc: T, key: string): T => {
const value = object[key];
const newPath = Array.isArray(object)
? `${path ? path : ''}[${key}]`
: [path, key].filter(Boolean).join(separator);
const isObject = [
typeof value === 'object',
value !== null,
!(value instanceof Date),
!(value instanceof RegExp),
!(Array.isArray(value) && value.length === 0),
].every(Boolean);
return isObject
? { ...acc, ...flatten(value, newPath, separator) }
: { ...acc, [newPath]: value };
}, {} as T);
}
Forked example: https://stackblitz.com/edit/typescript-pwsl83
I have put together a simple module, Flatify-obj based on this original gist with some additional tweaks and tests.
Usage
const flattenObject = require('flatify-obj');
flattenObject({foo: {bar: {unicorn: '🦄'}}})
//=> { 'foo.bar.unicorn': '🦄' }
flattenObject({foo: {unicorn: '🦄'}, bar: 'unicorn'}, {onlyLeaves: true});
//=> {unicorn: '🦄', bar: 'unicorn'}
For additional features PRs are welcome 🦄
Another one written in TypeScript: tree-to-flat-map.
you are the best!
@danzelbel like a charm!
Jewel!
This is my implementation, it just flattens every object in the parent object into one, without keying them with object.key
.
Working example
Code:
function flatten(obj = {}) {
const doneObject = {}
for (const [k, v] of Object.entries(obj)) {
if (typeof v == "object" && !(v instanceof Date) && !Array.isArray(v) && !(v instanceof regExp)) {
Object.assign(doneObject, flatten(v))
} else {
doneObject[k] = v
}
}
return doneObject
}
Thank you for the original implementation!
A version that leaves undefined behind, array as is and don't try to flatten primitive objects (Number, Boolean, BigInt and String essentially) :
const flatten = <T extends Record<string, any>>(object: T, path?: string): Record<string, any> =>
Object.entries(object).reduce((acc, [key, val]) => {
if (val === undefined) return acc;
if (path) key = `${path}.${key}`;
if (typeof val === 'object' && val !== null && !(val instanceof Date) && !(val instanceof RegExp) && !Array.isArray(val)) {
if (val !== val.valueOf()) {
return { ...acc, [key]: val.valueOf() };
}
return { ...acc, ...flatten(val, key) };
}
return { ...acc, [key]: val };
}, {});
Enjoy: