Skip to content

Instantly share code, notes, and snippets.

@iGoodie
Last active May 31, 2022 16:38
Show Gist options
  • Save iGoodie/a09b78110473e463bde8b26484bc4b82 to your computer and use it in GitHub Desktop.
Save iGoodie/a09b78110473e463bde8b26484bc4b82 to your computer and use it in GitHub Desktop.
Flatten JavaScript Object by Mapping Keys Recursively
export function flattenObject(
obj: object,
opts: {
prefix?: string;
postfix?: string;
delimiter?: string;
wordExtractor?: (text: string) => string[];
}
) {
const flattened: Record<string, any> = {};
const wordExtractor = opts.wordExtractor || extractWordsFromCamelCase;
const prefix = opts.prefix || "";
const postfix = opts.postfix || "";
const delimiter = opts.delimiter || ".";
for (let propertyName of Object.keys(obj)) {
const targetProperty = obj[propertyName];
const flattenedKey =
prefix + wordExtractor(propertyName).join(delimiter);
if (typeof targetProperty === "object") {
const flattenedTarget = flattenObject(targetProperty, { delimiter });
for (const [k, v] of Object.entries(flattenedTarget)) {
flattened[flattenedKey + delimiter + k + postfix] = v;
}
} else {
flattened[flattenedKey + postfix] = targetProperty;
}
}
return flattened;
}
// Default way to extract words. Assumes input string is in camelCase
function extractWordsFromCamelCase(text: string) {
return (text.charAt(0).toLowerCase() + text.substring(1))
.replace(/([A-Z])/g, ",$1")
.split(",")
.map((word) => word.toLowerCase());
}
@iGoodie
Copy link
Author

iGoodie commented May 31, 2022

Finally a very abstract example:

const myObject = {
  constant: 42,

  myObject: {
    foo: 1,
    bar: function () {},
    baz: [1, 2, 3],
  },
};

console.log({
  flattenedObject: flattenObject(myObject, {
    wordExtractor: (text) => [text],
  }),
});

Will print out:

{
  flattenedObject: {
    constant: 42,
    'myObject.foo': 1,
    'myObject.bar': [Function: bar],
    'myObject.baz.0': 1,
    'myObject.baz.1': 2,
    'myObject.baz.2': 3
  }
}

@iGoodie
Copy link
Author

iGoodie commented May 31, 2022

If you'd like arrays not to be flattened by their indices,
Simply replace typeof targetProperty === "object" check with targetProperty?.constructor.name === 'Object'

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