Related
I need to flatten a nested object. Need a one liner. Not sure what the correct term for this process is.
I can use pure Javascript or libraries, I particularly like underscore.
I've got ...
{
a:2,
b: {
c:3
}
}
And I want ...
{
a:2,
c:3
}
I've tried ...
var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "fred")
alert(JSON.stringify(resultObj));
Which works but I also need this to work ...
var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "john")
alert(JSON.stringify(resultObj));
Here you go:
Object.assign({}, ...function _flatten(o) { return [].concat(...Object.keys(o).map(k => typeof o[k] === 'object' ? _flatten(o[k]) : ({[k]: o[k]})))}(yourObject))
Summary: recursively create an array of one-property objects, then combine them all with Object.assign.
This uses ES6 features including Object.assign or the spread operator, but it should be easy enough to rewrite not to require them.
For those who don't care about the one-line craziness and would prefer to be able to actually read it (depending on your definition of readability):
Object.assign(
{},
...function _flatten(o) {
return [].concat(...Object.keys(o)
.map(k =>
typeof o[k] === 'object' ?
_flatten(o[k]) :
({[k]: o[k]})
)
);
}(yourObject)
)
Simplified readable example, no dependencies
/**
* Flatten a multidimensional object
*
* For example:
* flattenObject{ a: 1, b: { c: 2 } }
* Returns:
* { a: 1, c: 2}
*/
export const flattenObject = (obj) => {
const flattened = {}
Object.keys(obj).forEach((key) => {
const value = obj[key]
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
Object.assign(flattened, flattenObject(value))
} else {
flattened[key] = value
}
})
return flattened
}
Features
No dependencies
Works with null values
Works with arrays
Working example https://jsfiddle.net/webbertakken/jn613d8p/26/
Here is a true, crazy one-liner that flats the nested object recursively:
const flatten = (obj, roots=[], sep='.') => Object.keys(obj).reduce((memo, prop) => Object.assign({}, memo, Object.prototype.toString.call(obj[prop]) === '[object Object]' ? flatten(obj[prop], roots.concat([prop]), sep) : {[roots.concat([prop]).join(sep)]: obj[prop]}), {})
Multiline version, explained:
// $roots keeps previous parent properties as they will be added as a prefix for each prop.
// $sep is just a preference if you want to seperate nested paths other than dot.
const flatten = (obj, roots = [], sep = '.') => Object
// find props of given object
.keys(obj)
// return an object by iterating props
.reduce((memo, prop) => Object.assign(
// create a new object
{},
// include previously returned object
memo,
Object.prototype.toString.call(obj[prop]) === '[object Object]'
// keep working if value is an object
? flatten(obj[prop], roots.concat([prop]), sep)
// include current prop and value and prefix prop with the roots
: {[roots.concat([prop]).join(sep)]: obj[prop]}
), {})
An example:
const obj = {a: 1, b: 'b', d: {dd: 'Y'}, e: {f: {g: 'g'}}}
const flat = flatten(obj)
{
'a': 1,
'b': 'b',
'd.dd': 'Y',
'e.f.g': 'g'
}
Happy one-liner day!
ES6 Native, Recursive:
One-liner
const crushObj = (obj) => Object.keys(obj).reduce((acc, cur) => typeof obj[cur] === 'object' ? { ...acc, ...crushObj(obj[cur]) } : { ...acc, [cur]: obj[cur] } , {})
Expanded
const crushObj = (obj = {}) => Object.keys(obj || {}).reduce((acc, cur) => {
if (typeof obj[cur] === 'object') {
acc = { ...acc, ...crushObj(obj[cur])}
} else { acc[cur] = obj[cur] }
return acc
}, {})
Usage
const obj = {
a:2,
b: {
c:3
}
}
const crushed = crushObj(obj)
console.log(crushed)
// { a: 2, c: 3 }
My ES6 version:
const flatten = (obj) => {
let res = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
res = { ...res, ...flatten(value) };
} else {
res[key] = value;
}
}
return res;
}
It's not quite a one liner, but here's a solution that doesn't require anything from ES6. It uses underscore's extend method, which could be swapped out for jQuery's.
function flatten(obj) {
var flattenedObj = {};
Object.keys(obj).forEach(function(key){
if (typeof obj[key] === 'object') {
$.extend(flattenedObj, flatten(obj[key]));
} else {
flattenedObj[key] = obj[key];
}
});
return flattenedObj;
}
I like this code because it's a bit easier to understand.
Edit: I added some functionality I needed, so now it's a bit harder to understand.
const data = {
a: "a",
b: {
c: "c",
d: {
e: "e",
f: [
"g",
{
i: "i",
j: {},
k: []
}
]
}
}
};
function flatten(data, response = {}, flatKey = "", onlyLastKey = false) {
for (const [key, value] of Object.entries(data)) {
let newFlatKey;
if (!isNaN(parseInt(key)) && flatKey.includes("[]")) {
newFlatKey = (flatKey.charAt(flatKey.length - 1) == "." ? flatKey.slice(0, -1) : flatKey) + `[${key}]`;
} else if (!flatKey.includes(".") && flatKey.length > 0) {
newFlatKey = `${flatKey}.${key}`;
} else {
newFlatKey = `${flatKey}${key}`;
}
if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
flatten(value, response, `${newFlatKey}.`, onlyLastKey);
} else {
if(onlyLastKey){
newFlatKey = newFlatKey.split(".").pop();
}
if (Array.isArray(response)) {
response.push({
[newFlatKey.replace("[]", "")]: value
});
} else {
response[newFlatKey.replace("[]", "")] = value;
}
}
}
return response;
}
console.log(flatten(data));
console.log(flatten(data, {}, "data"));
console.log(flatten(data, {}, "data[]"));
console.log(flatten(data, {}, "data", true));
console.log(flatten(data, {}, "data[]", true));
console.log(flatten(data, []));
console.log(flatten(data, [], "data"));
console.log(flatten(data, [], "data[]"));
console.log(flatten(data, [], "data", true));
console.log(flatten(data, [], "data[]", true));
Demo https://stackblitz.com/edit/typescript-flatter
For insinde a typescript class use:
function flatten(data: any, response = {}, flatKey = "", onlyLastKey = false) {
for (const [key, value] of Object.entries(data)) {
let newFlatKey: string;
if (!isNaN(parseInt(key)) && flatKey.includes("[]")) {
newFlatKey = (flatKey.charAt(flatKey.length - 1) == "." ? flatKey.slice(0, -1) : flatKey) + `[${key}]`;
} else if (!flatKey.includes(".") && flatKey.length > 0) {
newFlatKey = `${flatKey}.${key}`;
} else {
newFlatKey = `${flatKey}${key}`;
}
if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
flatten(value, response, `${newFlatKey}.`, onlyLastKey);
} else {
if(onlyLastKey){
newFlatKey = newFlatKey.split(".").pop();
}
if (Array.isArray(response)) {
response.push({
[newFlatKey.replace("[]", "")]: value
});
} else {
response[newFlatKey.replace("[]", "")] = value;
}
}
}
return response;
}
This is a function I've got in my common libraries for exactly this purpose.
I believe I got this from a similar stackoverflow question, but cannot remember which (edit: Fastest way to flatten / un-flatten nested JSON objects - Thanks Yoshi!)
function flatten(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
This can then be called as follows:
var myJSON = '{a:2, b:{c:3}}';
var myFlattenedJSON = flatten(myJSON);
You can also append this function to the standard Javascript string class as follows:
String.prototype.flattenJSON = function() {
var data = this;
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
With which, you can do the following:
var flattenedJSON = '{a:2, b:{c:3}}'.flattenJSON();
Here are vanilla solutions that work for arrays, primitives, regular expressions, functions, any number of nested object levels, and just about everything else I could throw at them. The first overwrites property values in the manner that you would expect from Object.assign.
((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: Object.assign({}, ...function leaves(o) {
return [].concat.apply([], Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(key => v.hasOwnProperty(key))
|| Array.isArray(v))
? {[k]: v}
: leaves(v)
);
})
);
}(o))
})(o)
The second accumulates values into an array.
((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: (function () {
return Object.values((function leaves(o) {
return [].concat.apply([], !o ? [] : Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(k => v.hasOwnProperty(k))
|| (Array.isArray(v) && !v.some(el => typeof el === 'object')))
? {[k]: v}
: leaves(v)
);
})
);
}(o))).reduce((acc, cur) => {
return ((key) => {
acc[key] = !acc[key] ? [cur[key]]
: new Array(...new Set(acc[key].concat([cur[key]])))
})(Object.keys(cur)[0]) ? acc : acc
}, {})
})(o);
})(o)
Also please do not include code like this in production as it is terribly difficult to debug.
function leaves1(o) {
return ((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: Object.assign({}, ...function leaves(o) {
return [].concat.apply([], Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(key => v.hasOwnProperty(key))
|| Array.isArray(v))
? {[k]: v}
: leaves(v)
);
})
);
}(o))
})(o);
}
function leaves2(o) {
return ((o) => {
return o !== Object(o) || Array.isArray(o) ? {}
: (function () {
return Object.values((function leaves(o) {
return [].concat.apply([], !o ? [] : Object.entries(o)
.map(([k, v]) => {
return (( !v || typeof v !== 'object'
|| !Object.keys(v).some(k => v.hasOwnProperty(k))
|| (Array.isArray(v) && !v.some(el => typeof el === 'object')))
? {[k]: v}
: leaves(v)
);
})
);
}(o))).reduce((acc, cur) => {
return ((key) => {
acc[key] = !acc[key] ? [cur[key]]
: new Array(...new Set(acc[key].concat([cur[key]])))
})(Object.keys(cur)[0]) ? acc : acc
}, {})
})(o);
})(o);
}
const obj = {
l1k0: 'foo',
l1k1: {
l2k0: 'bar',
l2k1: {
l3k0: {},
l3k1: null
},
l2k2: undefined
},
l1k2: 0,
l2k3: {
l3k2: true,
l3k3: {
l4k0: [1,2,3],
l4k1: [4,5,'six', {7: 'eight'}],
l4k2: {
null: 'test',
[{}]: 'obj',
[Array.prototype.map]: Array.prototype.map,
l5k3: ((o) => (typeof o === 'object'))(this.obj),
}
}
},
l1k4: '',
l1k5: new RegExp(/[\s\t]+/g),
l1k6: function(o) { return o.reduce((a,b) => a+b)},
false: [],
}
const objs = [null, undefined, {}, [], ['non', 'empty'], 42, /[\s\t]+/g, obj];
objs.forEach(o => {
console.log(leaves1(o));
});
objs.forEach(o => {
console.log(leaves2(o));
});
Here's an ES6 version in TypeScript. It takes the best of answers given here and elsewhere. Some features:
Supports Date objects and converts them into ISO strings
Puts an underscore between the parent's and child's key (e.g. {a: {b: 'test'}} becomes {a_b: 'test'}
const flatten = (obj: Record<string, unknown>, parent?: string): Record<string, unknown> => {
let res: Record<string, unknown> = {}
for (const [key, value] of Object.entries(obj)) {
const propName = parent ? parent + '_' + key : key
const flattened: Record<string, unknown> = {}
if (value instanceof Date) {
flattened[key] = value.toISOString()
} else if(typeof value === 'object' && value !== null){
res = {...res, ...flatten(value as Record<string, unknown>, propName)}
} else {
res[propName] = value
}
}
return res
}
An example:
const example = {
person: {
firstName: 'Demo',
lastName: 'Person'
},
date: new Date(),
hello: 'world'
}
// becomes
const flattenedExample = {
person_firstName: 'Demo',
person_lastName: 'Person',
date: '2021-10-18T10:41:14.278Z',
hello: 'world'
}
Here is an actual oneliner of just 91 characters, using Underscore. (Of course. What else?)
var { reduce, isObject } = _;
var data = {
a: 1,
b: 2,
c: {
d: 3,
e: 4,
f: {
g: 5
},
h: 6
}
};
var tip = (v, m={}) => reduce(v, (m, v, k) => isObject(v) ? tip(v, m) : {...m, [k]: v}, m);
console.log(tip(data));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
Readable version:
var { reduce, isObject, extend } = _;
var data = {
a: 1,
b: 2,
c: {
d: 3,
e: 4,
f: {
g: 5
},
h: 6
}
};
// This function is passed to _.reduce below.
// We visit a single key of the input object. If the value
// itself is an object, we recursively copy its keys into
// the output object (memo) by calling tip. Otherwise we
// add the key-value pair to the output object directly.
function tipIteratee(memo, value, key) {
if (isObject(value)) return tip(value, memo);
return extend(memo, {[key]: value});
}
// The entry point of the algorithm. Walks over the keys of
// an object using _.reduce, collecting all tip keys in memo.
function tip(value, memo = {}) {
return _.reduce(value, tipIteratee, memo);
}
console.log(tip(data));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
Also works with Lodash.
To flatten only the first level of the object and merge duplicate object keys into an array:
var myObj = {
id: '123',
props: {
Name: 'Apple',
Type: 'Fruit',
Link: 'apple.com',
id: '345'
},
moreprops: {
id: "466"
}
};
const flattenObject = (obj) => {
let flat = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object' && value !== null) {
for (const [subkey, subvalue] of Object.entries(value)) {
// avoid overwriting duplicate keys: merge instead into array
typeof flat[subkey] === 'undefined' ?
flat[subkey] = subvalue :
Array.isArray(flat[subkey]) ?
flat[subkey].push(subvalue) :
flat[subkey] = [flat[subkey], subvalue]
}
} else {
flat = {...flat, ...{[key]: value}};
}
}
return flat;
}
console.log(flattenObject(myObj))
Object.assign requires a polyfill. This version is similar to previous ones, but it is not using Object.assign and it is still keep tracking of parent's name
const flatten = (obj, parent = null) => Object.keys(obj).reduce((acc, cur) =>
typeof obj[cur] === 'object' ? { ...acc, ...flatten(obj[cur], cur) } :
{ ...acc, [((parent) ? parent + '.' : "") + cur]: obj[cur] } , {})
const obj = {
a:2,
b: {
c:3
}
}
const flattened = flatten(obj)
console.log(flattened)
Here is a flatten function that correctly outputs array indexes.
function flatten(obj) {
const result = {};
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object') {
const nested = flatten(obj[key]);
for (const nestedKey of Object.keys(nested)) {
result[`${key}.${nestedKey}`] = nested[nestedKey];
}
} else {
result[key] = obj[key];
}
}
return result;
}
Example Input:
{
"first_name": "validations.required",
"no_middle_name": "validations.required",
"last_name": "validations.required",
"dob": "validations.required",
"citizenship": "validations.required",
"citizenship_identity": {
"name": "validations.required",
"value": "validations.required"
},
"address": [
{
"country_code": "validations.required",
"street": "validations.required",
"city": "validations.required",
"state": "validations.required",
"zipcode": "validations.required",
"start_date": "validations.required",
"end_date": "validations.required"
},
{
"country_code": "validations.required",
"street": "validations.required",
"city": "validations.required",
"state": "validations.required",
"zipcode": "validations.required",
"start_date": "validations.required",
"end_date": "validations.required"
}
]
}
Example Output:
const flattenedOutput = flatten(inputObj);
{
"first_name": "validations.required",
"no_middle_name": "validations.required",
"last_name": "validations.required",
"dob": "validations.required",
"citizenship": "validations.required",
"citizenship_identity.name": "validations.required",
"citizenship_identity.value": "validations.required",
"address.0.country_code": "validations.required",
"address.0.street": "validations.required",
"address.0.city": "validations.required",
"address.0.state": "validations.required",
"address.0.zipcode": "validations.required",
"address.0.start_date": "validations.required",
"address.0.end_date": "validations.required",
"address.1.country_code": "validations.required",
"address.1.street": "validations.required",
"address.1.city": "validations.required",
"address.1.state": "validations.required",
"address.1.zipcode": "validations.required",
"address.1.start_date": "validations.required",
"address.1.end_date": "validations.required"
}
function flatten(obj: any) {
return Object.keys(obj).reduce((acc, current) => {
const key = `${current}`;
const currentValue = obj[current];
if (Array.isArray(currentValue) || Object(currentValue) === currentValue) {
Object.assign(acc, flatten(currentValue));
} else {
acc[key] = currentValue;
}
return acc;
}, {});
};
let obj = {
a:2,
b: {
c:3
}
}
console.log(flatten(obj))
Demo
https://stackblitz.com/edit/typescript-flatten-json
Here goes, not thoroughly tested. Utilizes ES6 syntax too!!
loopValues(val){
let vals = Object.values(val);
let q = [];
vals.forEach(elm => {
if(elm === null || elm === undefined) { return; }
if (typeof elm === 'object') {
q = [...q, ...this.loopValues(elm)];
}
return q.push(elm);
});
return q;
}
let flatValues = this.loopValues(object)
flatValues = flatValues.filter(elm => typeof elm !== 'object');
console.log(flatValues);
I know its been very long, but it may be helpful for some one in the future
I've used recursion
let resObj = {};
function flattenObj(obj) {
for (let key in obj) {
if (!(typeof obj[key] == 'object')) {
// console.log('not an object', key);
resObj[key] = obj[key];
// console.log('res obj is ', resObj);
} else {
flattenObj(obj[key]);
}
}
return resObj;
}
Here's my TypeScript extension from #Webber's answer. Also supports dates:
private flattenObject(obj: any): any {
const flattened = {};
for (const key of Object.keys(obj)) {
if (isNullOrUndefined(obj[key])) {
continue;
}
if (typeof obj[key].getMonth === 'function') {
flattened[key] = (obj[key] as Date).toISOString();
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, this.flattenObject(obj[key]));
} else {
flattened[key] = obj[key];
}
}
return flattened;
}
const obj = {
a:2,
b: {
c:3
}
}
// recursive function for extracting keys
function extractKeys(obj) {
let flattenedObj = {};
for(let [key, value] of Object.entries(obj)){
if(typeof value === "object") {
flattenedObj = {...flattenedObj, ...extractKeys(value)};
} else {
flattenedObj[key] = value;
}
}
return flattenedObj;
}
// main code
let flattenedObj = extractKeys(obj);
console.log(flattenedObj);
I have tried to recursively convert an object fields from camelCase to UPPERCASE.
For some reason it wont work, I have tried many different ways from here in stackoverflow.
Would apprecaite for all the help, thanks.
const o = {
KeyFirst: "firstVal",
KeySecond: [
{
KeyThird: "thirdVal",
},
],
KeyFourth: {
KeyFifth: [
{
KeySixth: "sixthVal",
},
],
},
};
function renameKeys(obj) {
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
const modifiedKey = `${key[0].toLowerCase()}${key.slice(1)}`;
if (Array.isArray(value)) {
return {
...acc,
...{ [modifiedKey]: value.map(renameKeys) },
};
} else if (typeof value === "object") {
return renameKeys(value);
} else {
return {
...acc,
...{ [modifiedKey]: value },
};
}
}, {});
}
console.log(renameKeys(o));
You can recursively loop over the object and transform the keys to be uppercase.
const o = {
KeyFirst: { KeySecond: "secondVal" },
KeyThird: [{ KeyFourth: "fourthVal" }],
KeyFifth: {
KeySixth: [{ KeySeventh: "seventhVal" }],
},
};
function renameKeys(obj) {
if (Array.isArray(obj)) {
return obj.map((o) => renameKeys(o));
} else if (typeof obj === "object" && obj !== null) {
return Object.entries(obj).reduce(
(r, [k, v]) => ({ ...r, [k.toUpperCase()]: renameKeys(v) }),
{}
);
} else {
return obj;
}
}
console.log(renameKeys(o));
I'm looking for something kind of like Object.keys but that works for potentially nested objects. It also shouldn't include keys that have object/array values (it should only include keys with immediate string/number/boolean values).
Example A
Input
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP"
}
Expected output
[
"check_id",
"check_name",
"check_type"
]
Object.keys would work for flat cases like this, but not for nested cases:
Example B
Input
{
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
}
}
}
Expected output
[
"check_id",
"check_name",
"check_type",
"check_params.basic_auth",
"check_params.encryption.enabled"
]
Note that this does not include tags, check_params, check_params.params, or check_params.encryption since these values are arrays/objects.
The question
Is there a library that does this? How would you implement it so that it can work with any object, large and nested, or small?
You could use reduce like this:
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return res;
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Edit1: For the general case where you want to include arrays.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
}
return [...res, prefix + el];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"nested": [
{ "foo": 0 },
{ "bar": 1 }
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
A generator makes quick work of this kind of problem -
function* deepKeys (t, pre = [])
{ if (Array.isArray(t))
return
else if (Object(t) === t)
for (const [k, v] of Object.entries(t))
yield* deepKeys(v, [...pre, k])
else
yield pre.join(".")
}
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(Array.from(deepKeys(input)))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
Or a pure functional expression which eagerly computes all keys -
const deepKeys = (t, pre = []) =>
Array.isArray(t)
? []
: Object(t) === t
? Object
.entries(t)
.flatMap(([k, v]) => deepKeys(v, [...pre, k]))
: pre.join(".")
const input =
{check_id:12345,check_name:"Name of HTTP check",check_type:"HTTP",tags:["example_tag"],check_params:{basic_auth:false,params:["size"],encryption:{enabled:true,testNull:null,}}}
console.log(deepKeys(input))
[ "check_id"
, "check_name"
, "check_type"
, "check_params.basic_auth"
, "check_params.encryption.enabled"
, "check_params.encryption.testNull"
]
You could check the keys and iterate otherwise push the path to the result set.
function getKeys(object) {
function iter(o, p) {
if (Array.isArray(o)) { return; }
if (o && typeof o === 'object') {
var keys = Object.keys(o);
if (keys.length) {
keys.forEach(function (k) { iter(o[k], p.concat(k)); });
}
return;
}
result.push(p.join('.'));
}
var result = [];
iter(object, []);
return result;
}
var object = { check_id: 12345, check_name: "Name of HTTP check", check_type: "HTTP", tags: ["example_tag"], check_params: { basic_auth: false, params: ["size"], encryption: { enabled: true } } };
console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use for...in and create recursive function.
var obj = {"check_id":12345,"check_name":"Name of HTTP check","check_type":"HTTP","tags":["example_tag"],"check_params":{"basic_auth":false,"params":["size",{"a":"b"}],"encryption":{"enabled":true}}}
var keys = []
function getKeys(data, k = '') {
for (var i in data) {
var rest = k.length ? '.' + i : i
if (typeof data[i] == 'object') {
if (!Array.isArray(data[i])) {
getKeys(data[i], k + rest)
}
} else keys.push(k + rest)
}
}
getKeys(obj)
console.log(keys)
var json = {
id: '1234',
test: 'terst',
user : {
name: '',
details: {
address: {
add2: {
prim: "",
sec: ""
},
add1: '',
}
}
},
salary: {
cur: 1234,
net: 89797
},
age: 12
}
let arr = [];
let initialObj = {};
function getKeys(obj, parentK=''){
initialObj = arr.length === 0 ? obj: initialObj;
const entries = Object.entries(obj);
for(let i=0; i<entries.length; i++) {
const key = entries[i][0];
const val = entries[i][1];
const isRootElement = initialObj.hasOwnProperty(key);
parentK = isRootElement ? key: parentK+'.'+key;
arr.push(parentK)
if(typeof val === 'object' && val!==null && !Array.isArray(val)){
getKeys(val, parentK);
}
}
}
getKeys(json)
console.log('arr final---', arr);
Further enhanced above recommendation to return all keys including array.
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
return [...res,`${el}: ${obj[el].toString()}`];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res,...keyify(obj[el],`${prefix}${el}.`)];
}
return [...res,`${prefix}${el}: ${obj[el]}`];
}, []);
const input = {
"check_id":12345,
"check_name":"Name of HTTP check",
"check_type":"HTTP",
"tags":[
"example_tag"
],
"check_params":{
"basic_auth":false,
"params":[
"size"
],
"encryption": {
"enabled": true,
"testNull": null,
}
}
};
const output = keyify(input);
console.log(output);
Expected output:
[
'check_id: 12345',
'check_name: Name of HTTP check',
'check_type: HTTP',
'tags: example_tag',
'check_params.basic_auth: false',
'params: size',
'check_params.encryption.enabled: true',
'check_params.encryption.testNull: null'
]
Is this what you mean?
http://jsfiddle.net/robbiemilejczak/hfe12brb/1/
I couldn't do it with vanilla JS, and this is a pretty hacky solution that relies on lodash. Basically leverages lodashs _.forIn and _.isArray functions to iterate over an object. Also this will only go 1 layer deep, so objects inside of nested objects will be ignored. It does produce your expected output though, so I'd say it's a decent starting point.
I have a javascript object structured like this;
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
Given an array of keys ['brand', 'group', 'newGroup', 'newSubGroup'] I want to split the keys into found and missing keys. So for the structure above I should get back;
present = ['brand', 'group']
missing = ['newGroup', 'newSubGroup']
I'm using ES6 and have lodash available, but struggling to find a clean way to produce this.
This is not to just check existence, it's recursively find the keys and return those present and the remaining ones.
Here's a pretty sketchy way that works.
const find = (keys, obj) => {
const string = JSON.stringify(obj);
return keys.reduce(({ present, missing }, key) => {
const match = string.match(new RegExp(`"${key}":`));
if (match) {
present.push(key);
} else {
missing.push(key);
}
return { present, missing };
}, { present: [], missing: [] });
}
You can use this function made for you ;)
var getAttrs = function(obj) {
return [].concat.apply([], Object.keys(obj).map(function (key) {
var results = [key]
if (typeof obj[key] === 'object') {
Array.prototype.push.apply(results, getAttrs(obj[key]))
}
return results
}))
}
It return the list of properties and children properties.
getAttrs({brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}})
> ["brand", "group", "subGroup", "items", "otherSub", "items"]
And you can use it like so:
var lookingFor = ['brand', 'group', 'newGroup', 'newSubGroup']
var existings = getAttrs(obj)
var missings = []
var presents = []
lookingFor.forEach(attr => {
if (existings.indexOf(attr) === -1) {
missings.push(attr)
} else {
presents.push(attr)
}
})
I wrote a function to recursively get unique keys from a nested object, then filtered the array of all the keys you mentioned checking which were present in the result of my function.
var thisObject = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
};
var arr_full = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
var key_array = [];
function addToKeyArray( key_array, object ){
for( var key in object ){
// only get unique keys
if( key_array.indexOf( key ) === -1 ){
key_array.push( key );
}
// concat the result of calling this function recurrsively on object[key]
key_array.concat( addToKeyArray( key_array, object[key] ) );
}
return key_array;
}
var test = addToKeyArray( [], thisObject );
var missing = arr_full.filter( function( el ) {
return test.indexOf( el ) < 0;
});
console.log( test );
console.log( missing )
You can create recursive function using for...in loop inside another function and return object as result..
var obj = {"brand":{"group":{"subGroup":{"items":[]},"otherSub":{"items":[]}}}}
var keys = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
function findKeys(data, keys) {
keys = keys.slice();
function findPresent(data, keys) {
var result = []
for(var i in data) {
if(typeof data[i] == 'object') result.push(...findPresent(data[i], keys))
var index = keys.indexOf(i);
if(index != -1) result.push(...keys.splice(index, 1))
}
return result
}
return {present: findPresent(data, keys), missing: keys}
}
console.log(findKeys(obj, keys))
To keep things clean and readable you can use "for in", inside a nested function for your recursion.
function recur(obj) {
let preMiss = {
present: [],
missing: []
}
let root = traverse => {
for (let key in traverse) {
if (Array.isArray(traverse[key].items)) {
preMiss.missing.push(key);
}
if (typeof traverse[key] === 'object' && !Array.isArray(traverse[key].items)) {
preMiss.present.push(key);
root(traverse[key])
}
}
}
root(obj);
return preMiss;
}
const object = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
console.log(Object.entries(recur(object)));
var toFind = ['brand', 'group', 'newGroup', 'newSubGroup'],
found = [];
var o = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
//called with every property and its value
function process(key,value) {
var i = toFind.indexOf(key);
if(i !== -1){
found.push(key);
toFind.splice(i, 1);
}
}
function traverse(o,func) {
if(!toFind.length) return;
for (var i in o) {
func.apply(this,[i,o[i]]);
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
traverse(o[i],func);
}
}
}
traverse(o,process);
console.log(found); // present
console.log(toFind); // absent
Traverse method taken from https://stackoverflow.com/a/722732/1335165
Even though this question is a bit older, I want to present a rather short solution to the problem.
const recursivelyGetKeys = obj => Object.keys(obj).map(key => typeof obj[key] === 'object'
? [...recursivelyGetKeys(obj[key]), key] : [key]).reduce((p, c) => [...p, ...c], [])
This function will return all keys in the object, so a call to the array arr with
const arr = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
will output:
const keys = recursivelyGetKeys(arr) // = ["items", "subGroup", "items", "otherSub", "group", "brand"]
Now to find the intersection set of this and find = ['brand', 'group', 'newGroup', 'newSubGroup'], do:
const found = keys.filter(key => find.some(val === key))
const missing = keys.filter(key => find.every(val !== key))
I have JSON returned from an API like so:
Contacts: [{ GivenName: "Matt", FamilyName: "Berry" }]
To keep this consistent with my code style (camelCase - lower case first letter) I want to transform the array to produce the following:
contacts: [{ givenName: "Matt", familyName: "Berry" }]
What's the easiest/best way to do this? Create a new Contact object and iterate over all the contacts in the returned array?
var jsonContacts = json["Contacts"],
contacts= [];
_.each(jsonContacts , function(item){
var contact = new Contact( item.GivenName, item.FamilyName );
contacts.push(contact);
});
or can I map the original array or transform it somehow?
If you would use lodash instead of underscore, this would do:
_.mapKeys(obj, (v, k) => _.camelCase(k))
This would convert both TitleCase and snake_case to camelCase. Note that it is not recursive though.
Here's a reliable, recursive function that will properly camelCase all of a JavaScript object's properties:
function toCamel(o) {
var newO, origKey, newKey, value
if (o instanceof Array) {
return o.map(function(value) {
if (typeof value === "object") {
value = toCamel(value)
}
return value
})
} else {
newO = {}
for (origKey in o) {
if (o.hasOwnProperty(origKey)) {
newKey = (origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey).toString()
value = o[origKey]
if (value instanceof Array || (value !== null && value.constructor === Object)) {
value = toCamel(value)
}
newO[newKey] = value
}
}
}
return newO
}
Test:
var obj = {
'FirstName': 'John',
'LastName': 'Smith',
'BirthDate': new Date(),
'ArrayTest': ['one', 'TWO', 3],
'ThisKey': {
'This-Sub-Key': 42
}
}
console.log(JSON.stringify(toCamel(obj)))
Output:
{
"firstName":"John",
"lastName":"Smith",
"birthDate":"2017-02-13T19:02:09.708Z",
"arrayTest": [
"one",
"TWO",
3
],
"thisKey":{
"this-Sub-Key":42
}
}
You can do this with this recursive function (with lodash and ES6):
import { camelCase } from 'lodash';
const camelizeKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map(v => camelizeKeys(v));
} else if (obj != null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[camelCase(key)]: camelizeKeys(obj[key]),
}),
{},
);
}
return obj;
};
Test:
const obj = {
'FirstName': 'John',
'LastName': 'Smith',
'BirthDate': new Date(),
'ArrayTest': ['one', 'TWO', 3],
'ThisKey': {
'This-Sub-Key': 42
}
}
console.log(JSON.stringify(camelizeKeys(obj)))
Output:
{
"firstName": "John",
"lastName": "Smith",
"birthDate": "2018-05-31T09:03:57.844Z",
"arrayTest":[
"one",
"TWO",
3
],
"thisKey":{
"thisSubKey": 42
}
}
To change a plain object's keys from snake_case to camelCase recursively try the following
(which uses Lodash):
function objectKeysToCamelCase(snake_case_object) {
var camelCaseObject = {};
_.forEach(
snake_case_object,
function(value, key) {
if (_.isPlainObject(value) || _.isArray(value)) { // checks that a value is a plain object or an array - for recursive key conversion
value = objectKeysToCamelCase(value); // recursively update keys of any values that are also objects
}
camelCaseObject[_.camelCase(key)] = value;
}
)
return camelCaseObject;
};
test in this PLUNKER
Note: also works recursively for objects within arrays
Using lodash and ES6, this will replace all keys recursively to camelcase:
const camelCaseKeys = (obj) => {
if (!_.isObject(obj)) {
return obj;
} else if (_.isArray(obj)) {
return obj.map((v) => camelCaseKeys(v));
}
return _.reduce(obj, (r, v, k) => {
return {
...r,
[_.camelCase(k)]: camelCaseKeys(v)
};
}, {});
};
Just use humps
humps.camelize('hello_world');
humps.camelizeKeys(object, options); // will work through entire object
https://www.npmjs.com/package/humps
This is a great use case for axios interceptors
Basically, define a client class and attach a before/after interceptor that converts the request/response data.
export default class Client {
get(url, data, successCB, catchCB) {
return this._perform('get', url, data, successCB, catchCB);
}
post(url, data, successCB, catchCB) {
return this._perform('post', url, data, successCB, catchCB);
}
_perform(method, url, data, successCB, catchCB) {
// https://github.com/axios/axios#interceptors
// Add a response interceptor
axios.interceptors.response.use((response) => {
response.data = toCamelCase(response.data);
return response;
}, (error) => {
error.data = toCamelCase(error.data);
return Promise.reject(error);
});
// Add a request interceptor
axios.interceptors.request.use((config) => {
config.data = toSnakeCase(config.data);
return config;
}, (error) => {
return Promise.reject(error);
});
return axios({
method: method,
url: API_URL + url,
data: data,
headers: {
'Content-Type': 'application/json',
},
}).then(successCB).catch(catchCB)
}
}
Here's a gist with a longer example using React/axios.
there's a nice npm module for this..
https://www.npmjs.com/package/camelcase-keys
npm install camelcase-keys
const camelcaseKeys = require( "camelcase-keys" );
camelcaseKeys( { Contacts: [ { GivenName: "Matt", FamilyName: "Berry" } ] }, { deep: true } );
will return...
{ contacts: [ { givenName: "Matt", familyName: "Berry" } ] }
This solution based on the plain js solution above, uses loadash and Keeps an array if passed as a parameter and Only change the Keys
function camelCaseObject(o) {
let newO, origKey, value
if (o instanceof Array) {
newO = []
for (origKey in o) {
value = o[origKey]
if (typeof value === 'object') {
value = camelCaseObject(value)
}
newO.push(value)
}
} else {
newO = {}
for (origKey in o) {
if (o.hasOwnProperty(origKey)) {
newO[_.camelCase(origKey)] = o[origKey]
}
}
}
return newO
}
// Example
const obj = [
{'my_key': 'value'},
{'Another_Key':'anotherValue'},
{'array_key':
[{'me_too':2}]
}
]
console.log(camelCaseObject(obj))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
Using lodash, you can do it like this:
export const toCamelCase = obj => {
return _.reduce(obj, (result, value, key) => {
const finalValue = _.isPlainObject(value) || _.isArray(value) ? toCamelCase(value) : value;
return { ...result, [_.camelCase(key)]: finalValue };
}, {});
};
Well I took up the challenge and think I figured it out:
var firstToLower = function(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
};
var firstToUpper = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
var mapToJsObject = function(o) {
var r = {};
$.map(o, function(item, index) {
r[firstToLower(index)] = o[index];
});
return r;
};
var mapFromJsObject = function(o) {
var r = {};
$.map(o, function(item, index) {
r[firstToUpper(index)] = o[index];
});
return r;
};
// Map to
var contacts = [
{
GivenName: "Matt",
FamilyName: "Berry"
},
{
GivenName: "Josh",
FamilyName: "Berry"
},
{
GivenName: "Thomas",
FamilyName: "Berry"
}
];
var mappedContacts = [];
$.map(contacts, function(item) {
var m = mapToJsObject(item);
mappedContacts.push(m);
});
alert(mappedContacts[0].givenName);
// Map from
var unmappedContacts = [];
$.map(mappedContacts, function(item) {
var m = mapFromJsObject(item);
unmappedContacts.push(m);
});
alert(unmappedContacts[0].GivenName);
Property converter (jsfiddle)
The trick is handling the objects as arrays of object properties.
Here's handy library you might wanna try:
https://www.npmjs.com/package/camelize2
You simply need to install it with npm install --save camelize2 and then
const camelize = require('camelize2')
const response = {
Contacts: [{ GivenName: "Matt", FamilyName:"Berry" }]
}
const camelizedResponse = camelize(response)
Solution similar to #brandonscript, but in more ES6-functional way:
const camelCaseString = str => (
(str.charAt(0).toLowerCase() + str.slice(1) || str).toString()
);
const objectToCamelCase = val => {
if (typeof val != 'object' || val === null) {
return val;
}
if (val instanceof Array) {
return val.map(objectToCamelCase);
}
return Object.keys(val)
.filter(prop => val.hasOwnProperty(prop))
.map(prop => ({[camelCaseString(prop)]: objectToCamelCase(val[prop])}))
.reduce((prev, current) => ({...prev, ...current}))
};
// Example:
let converted = objectToCamelCase({UserId: 1, Hobbies: [{Id: 1, Label: "Read"}], Name: "John Doe"});
console.log(converted)
I needed a generic method that accepted an array or object. This is what I'm using (I borrowed KyorCode's firstToLower() implementation):
function convertKeysToCamelCase(obj) {
if (!obj || typeof obj !== "object") return null;
if (obj instanceof Array) {
return $.map(obj, function(value) {
return convertKeysToCamelCase(value);
});
}
var newObj = {};
$.each(obj, function(key, value) {
key = key.charAt(0).toLowerCase() + key.slice(1);
if (typeof value == "object" && !(value instanceof Array)) {
value = convertKeysToCamelCase(value);
}
newObj[key] = value;
});
return newObj;
};
Example calls:
var contact = { GivenName: "Matt", FamilyName:"Berry" };
console.log(convertKeysToCamelCase(contact));
// logs: Object { givenName="Matt", familyName="Berry"}
console.log(convertKeysToCamelCase([contact]));
// logs: [Object { givenName="Matt", familyName="Berry"}]
console.log(convertKeysToCamelCase("string"));
// logs: null
console.log(contact);
// logs: Object { GivenName="Matt", FamilyName="Berry"}
Took the challenge with lodash and some es6+ features
Here is my implementation with the reduce function.
function deeplyToCamelCase(obj) {
return _.reduce(obj, (camelCaseObj, value, key) => {
const convertedDeepValue = _.isPlainObject(value) || _.isArray(value)
? deeplyToCamelCase(value)
: value;
return { ...camelCaseObj, [_.camelCase(key)] : convertedDeepValue };
}, {});
};
Use lodash ...
function isPrimitive (variable) {
return Object(variable) !== variable
}
function toCamel (variable) {
if (isPrimitive(variable)) {
return variable
}
if (_.isArray(variable)) {
return variable.map(el => toCamel(el))
}
const newObj = {}
_.forOwn(variable, (value, key) => newObj[_.camelCase(key)] = toCamel(value))
return newObj
}
This function loop recursively through the object keys and using lodash returns a new object with every field converted to camelCase. It works also with arrays, nested arrays, nested objects.
function deepCamelCase (obj) {
const c = {}
if (typeof obj !== 'object') return obj
_.mapKeys(obj, (v, k) => {
let w = {}
if (typeof v === 'object') {
if (Array.isArray(v)) {
const k = []
for (const i of v) {
k.push(deepCamelCase(i))
}
} else {
_.mapValues(v, (n, m) => {
if (Array.isArray(n)) {
const k = []
for (const i of n) {
k.push(deepCamelCase(i))
}
w[_.camelCase(m)] = k
} else {
w[_.camelCase(m)] = deepCamelCase(n)
}
})
}
} else {
w = v
}
c[_.camelCase(k)] = w
})
return c
}
Updated code using the reference from https://plnkr.co/edit/jtsRo9yU12geH7fkQ0WL?p=preview
This handles the Objects with array with objects inside it too and so on, by keeping arrays as arrays (which you can iterate over using map)
function snakeToCamelCase(snake_case_object){
var camelCaseObject;
if (isPlainObject(snake_case_object)) {
camelCaseObject = {};
}else if(isArray(snake_case_object)){
camelCaseObject = [];
}
forEach(
snake_case_object,
function(value, key) {
if (isPlainObject(value) || isArray(value)) {
value = snakeToCamelCase(value);
}
if (isPlainObject(camelCaseObject)) {
camelCaseObject[camelCase(key)] = value;
}else if(isArray(camelCaseObject)){
camelCaseObject.push(value);
}
}
)
return camelCaseObject;
}
This is my take; more readable and with less nesting than brandoncode's implementation, and with more room for handling edge cases like Date (which isn't handled, by the way) or null:
function convertPropertiesToCamelCase(instance) {
if (instance instanceof Array) {
var result = [];
for (var i = 0; i < instance.length; i++) {
result[i] = convertPropertiesToCamelCase(instance[i]);
}
return result;
}
if (typeof instance != 'object') {
return instance;
}
var result = {};
for (var key in instance) {
if (!instance.hasOwnProperty(key)) {
continue;
}
result[key.charAt(0).toLowerCase() + key.substring(1)] = convertPropertiesToCamelCase(instance[key]);
}
return result;
}
Building on goredwards answer (which didn't handle the array fields correctly)
function objectKeysToCamelCase(snake_case_object) {
let camelCaseObject = {}
_.forEach(
snake_case_object,
function(value, key) {
if (_.isPlainObject(value)) {
value = objectKeysToCamelCase(value)
} else if (_.isArray(value)) {
value = value.map(v => _.isPlainObject(v) ? objectKeysToCamelCase(v) : v)
}
camelCaseObject[_.camelCase(key)] = value
},
)
return camelCaseObject
}
here is code I found for it, not fully tested though, but worth sharing.
It is far more readable than other answers, not sure about performance.
test it http://jsfiddle.net/ms734bqn/1/
const toCamel = (s) => {
return s.replace(/([-_][a-z])/ig, ($1) => {
return $1.toUpperCase()
.replace('-', '')
.replace('_', '');
});
};
const isArray = function (a) {
return Array.isArray(a);
};
const isObject = function (o) {
return o === Object(o) && !isArray(o) && typeof o !== 'function';
};
const keysToCamel = function (o) {
if (isObject(o)) {
const n = {};
Object.keys(o)
.forEach((k) => {
n[toCamel(k)] = keysToCamel(o[k]);
});
return n;
} else if (isArray(o)) {
return o.map((i) => {
return keysToCamel(i);
});
}
return o;
};
Pure JavaScript, shoud work fine
function convertKeysToCamelCase(object) {
if(object === undefined || object === null || typeof object !== "object") {
return object;
} else {
if(Array.isArray(object)) {
return object.map(item => convertKeysToCamelCase(item));
} else {
return Object.entries(object).reduce((result, [key, value]) => {
result[key.charAt(0).toLowerCase() + key.slice(1)] = convertKeysToCamelCase(value);
return result;
}, {});
}
}
}
you can do this simply by using json-case-convertor
const jcc = require('json-case-convertor')
const jsonData = ''//you json data to convert
const camelCasedJson = jcc.camelCaseKeys(jsonData) //Convert all the keys of object to snake case
This will handle all cascaded object as well
Convert object keys to camelCase with deep.
import _ from 'lodash';
export function objectKeysToCamelCase(entity) {
if (!_.isObject(entity)) return entity;
let result;
result = _.mapKeys(entity, (value, key) => _.camelCase(key));
result = _.mapValues(result, (value) => objectKeysToCamelCase(value));
return result;
}