For a deeply nested object or array of objects, we might want to replace all the keys in some way, such as modifying their case (camel case, snake case, etc.) or adding an underscore at the start of each key or on both sides. Or we may like to change the keys in any other way.
Following is the generic JavaScript code and test object for that, followed by the complete code with specific examples:
The Generic Code
The code below is a generic function replaceAllObjKeys,
to which we recursively pass obj
and getNewKey
parameters.
obj
here could be a JavaScript object or array. The second parameter, getNewKey
is a helper method. We need to decide what getNewKey
method should be depending on how we need to change all the keys (capitalize, uppercase, lowercase, snake case, etc.).
const replaceAllObjKeys = (obj, getNewKey) => {
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
replaceAllObjKeys(obj[i], getNewKey);
}
}
else if (typeof obj === "object") {
for (const key in obj) {
const newKey = getNewKey(key);
obj[newKey] = obj[key];
if (key !== newKey) {
delete obj[key];
}
replaceAllObjKeys(obj[newKey], getNewKey);
}
}
return obj;
};
Notice that once we get the new key, we assign it the reference of the existing value and delete the old key (deleting the key does not affect the value in any way as it’s just a memory reference).
Before deleting, we need to verify that the old and new keys are different because, in some cases, they might remain the same. For instance, when you change the key a_b_c
to snake case, it’ll remain a_b_c
. Deleting the key in such a case will remove the only key available, and the reference to its value will be lost.
The Test Object
For the specific examples below, I’ll be using the following test data:
const deeplyNestedObj = {
id: 1,
"a b C": {
"d e f": {
ghi: "ghi",
jkL: "jkl"
}
},
mno: [
{
onm: "onm"
},
{
nOm: "nom"
}
],
"p q r": "pqr",
Stu: {
vwx: "vwx",
y: {
Z: "z"
}
}
};
Library Requirements
Instead of reinventing the wheel, I’m going to use the lodash
library for most of the case changes. lodash
is not strictly necessary. If you have your own implementation for key modification, you can pass it as getNewKey
helper method. I’m using two small custom helper methods below startWithUnderscore
and addUnderscoreOnBothSides
.
Install lodash
:
npm i lodash
And include the required methods in the code:
import {
cloneDeep,
capitalize,
camelCase,
kebabCase,
snakeCase,
startCase,
upperCase,
lowerCase,
upperFirst,
lowerFirst,
toUpper,
toLower,
} from "lodash";
(Here, cloneDeep
is for not modifying the original example object before each subsequent call to replaceAllObjKeys
, as I’m making multiple calls in the same example. It won’t be required for practical use.)
The reference to the methods used here can be found under “String” section in lodash’s docs.
The Complete Code
import {
cloneDeep,
capitalize,
camelCase,
kebabCase,
snakeCase,
startCase,
upperCase,
lowerCase,
upperFirst,
lowerFirst,
toUpper,
toLower,
} from "lodash";
const deeplyNestedObj = {
id: 1,
"a b C": {
"d e f": {
ghi: "ghi",
jkL: "jkl"
}
},
mno: [
{
onm: "onm"
},
{
nOm: "nom"
}
],
"p q r": "pqr",
Stu: {
vwx: "vwx",
y: {
Z: "z"
}
}
}
const replaceAllObjKeys = (obj, getNewKey) => {
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
replaceAllObjKeys(obj[i], getNewKey);
}
}
else if (typeof obj === "object") {
for (const key in obj) {
const newKey = getNewKey(key);
obj[newKey] = obj[key];
if (key !== newKey) {
delete obj[key];
}
replaceAllObjKeys(obj[newKey], getNewKey);
}
}
return obj;
};
const startWithUnderscore = (key) => `_${key}`;
const addUnderscoreOnBothSides = (key) => `_${key}_`;
console.log(
"*** To Upper *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), toUpper),
"\n\n"
);
console.log(
"*** Upper Case *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), upperCase),
"\n\n"
);
console.log(
"*** Upper First *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), upperFirst),
"\n\n"
);
console.log(
"*** To Lower *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), toLower),
"\n\n"
);
console.log(
"*** Lower Case *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), lowerCase),
"\n\n"
);
console.log(
"*** Lower First *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), lowerFirst),
"\n\n"
);
console.log(
"*** Capitalize *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), capitalize),
"\n\n"
);
console.log(
"*** Camel Case *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), camelCase),
"\n\n"
);
console.log(
"*** Kebab Case *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), kebabCase),
"\n\n"
);
console.log(
"*** Snake Case *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), snakeCase),
"\n\n"
);
console.log(
"*** Start Case *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), startCase),
"\n\n"
);
console.log(
"*** Start With Underscore *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), startWithUnderscore),
"\n\n"
);
console.log(
"*** Add Underscore on Both Sides *** \n",
replaceAllObjKeys(cloneDeep(deeplyNestedObj), addUnderscoreOnBothSides),
"\n\n"
);
The Result
*** To Upper ***
{
ID: 1,
'A B C': { 'D E F': { GHI: 'ghi', JKL: 'jkl' } },
MNO: [ { ONM: 'onm' }, { NOM: 'nom' } ],
'P Q R': 'pqr',
STU: { VWX: 'vwx', Y: { Z: 'z' } }
}
*** Uppercase ***
{
ID: 1,
'A B C': { 'D E F': { GHI: 'ghi', 'JK L': 'jkl' } },
MNO: [ { ONM: 'onm' }, { 'N OM': 'nom' } ],
'P Q R': 'pqr',
STU: { VWX: 'vwx', Y: { Z: 'z' } }
}
*** Upper First ***
{
Stu: { Vwx: 'vwx', Y: { Z: 'z' } },
Id: 1,
'A b C': { 'D e f': { Ghi: 'ghi', JkL: 'jkl' } },
Mno: [ { Onm: 'onm' }, { NOm: 'nom' } ],
'P q r': 'pqr'
}
*** To Lower ***
{
id: 1,
mno: [ { onm: 'onm' }, { nom: 'nom' } ],
'p q r': 'pqr',
'a b c': { 'd e f': { ghi: 'ghi', jkl: 'jkl' } },
stu: { vwx: 'vwx', y: { z: 'z' } }
}
*** Lowercase ***
{
id: 1,
mno: [ { onm: 'onm' }, { 'n om': 'nom' } ],
'p q r': 'pqr',
'a b c': { 'd e f': { ghi: 'ghi', 'jk l': 'jkl' } },
stu: { vwx: 'vwx', y: { z: 'z' } }
}
*** Lower First ***
{
id: 1,
'a b C': { 'd e f': { ghi: 'ghi', jkL: 'jkl' } },
mno: [ { onm: 'onm' }, { nOm: 'nom' } ],
'p q r': 'pqr',
stu: { vwx: 'vwx', y: { z: 'z' } }
}
*** Capitalize ***
{
Stu: { Vwx: 'vwx', Y: { Z: 'z' } },
Id: 1,
'A b c': { 'D e f': { Ghi: 'ghi', Jkl: 'jkl' } },
Mno: [ { Onm: 'onm' }, { Nom: 'nom' } ],
'P q r': 'pqr'
}
*** Camelcase ***
{
id: 1,
mno: [ { onm: 'onm' }, { nOm: 'nom' } ],
aBC: { dEF: { ghi: 'ghi', jkL: 'jkl' } },
pQR: 'pqr',
stu: { vwx: 'vwx', y: { z: 'z' } }
}
*** Kebabcase ***
{
id: 1,
mno: [ { onm: 'onm' }, { 'n-om': 'nom' } ],
'a-b-c': { 'd-e-f': { ghi: 'ghi', 'jk-l': 'jkl' } },
'p-q-r': 'pqr',
stu: { vwx: 'vwx', y: { z: 'z' } }
}
*** Snakecase ***
{
id: 1,
mno: [ { onm: 'onm' }, { n_om: 'nom' } ],
a_b_c: { d_e_f: { ghi: 'ghi', jk_l: 'jkl' } },
p_q_r: 'pqr',
stu: { vwx: 'vwx', y: { z: 'z' } }
}
*** Startcase ***
{
Stu: { Vwx: 'vwx', Y: { Z: 'z' } },
Id: 1,
'A B C': { 'D E F': { Ghi: 'ghi', 'Jk L': 'jkl' } },
Mno: [ { Onm: 'onm' }, { 'N Om': 'nom' } ],
'P Q R': 'pqr'
}
*** Start With Underscore ***
{
_id: 1,
'_a b C': { '_d e f': { _ghi: 'ghi', _jkL: 'jkl' } },
_mno: [ { _onm: 'onm' }, { _nOm: 'nom' } ],
'_p q r': 'pqr',
_Stu: { _vwx: 'vwx', _y: { _Z: 'z' } }
}
*** Add Underscore on Both Sides ***
{
_id_: 1,
'_a b C_': { '_d e f_': { _ghi_: 'ghi', _jkL_: 'jkl' } },
_mno_: [ { _onm_: 'onm' }, { _nOm_: 'nom' } ],
'_p q r_': 'pqr',
_Stu_: { _vwx_: 'vwx', _y_: { _Z_: 'z' } }
}
See also
- SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
- Yup Date Format Validation With Moment JS
- Yup Number Validation: Allow Empty String
- Exactly Same Query Behaving Differently in Mongo Client and Mongoose
- JavaScript Unit Testing JSON Schema Validation
- Reduce JS Size With Constant Strings
- JavaScript SDK