An application of Array.Reduce() - Capitalize Strings in Nested-object

It has been a few days since my last post. I was quite busy with development the company’s ERP platform. Finally, today I have some time to share one interesting case that I encountered in the process of developing our company’s job application portal.

Problem

The portal has a main component which is the job application form. It requires the users to fill in all kinds of fields including personal information, education records and past working experiences. After completing the form development, our beloved PM requested another feature, when submitting the form, transform some specified fields to uppercase texts and then pass to the backend.

Now the problem is very cleared. I have a complex object which stores the whole application information, which is something like the code showed below, containing nested attributes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const application = {
countryApplied: 'Singapore',
personalInfo: {
name: 'Peter',
age: 24,
email: 'peter@tmail.com',
emergencyContacts: {
name: 'Ronald',
age: 49,
relationship: 'father',
email: 'ronald@gotmail.com'
},
}
}

The PM would like to make the country applied, the applicant’s name, ermergency contact’s name, relationship to be capitalized. (The real application object contains many more attributes. Here is a shorter version illustration)

In the end, the object should become as below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const application = {
countryApplied: 'SINGAPORE',
personalInfo: {
name: 'PETER',
age: 24,
email: 'peter@tmail.com',
emergencyContacts: {
name: 'RONALD',
age: 49,
relationship: 'FATHER',
email: 'ronald@gotmail.com'
},
}
}

Analysis

To automate the process, I hope to use an array to list all the fields which I need to capitalize, using a string to represent the deeply nested key, separated by comma.

1
2
3
4
5
6
const requiredFields = [
'countryApplied',
'personalInfo.name',
'personalInfo.emergencyContacts.name',
'personalInfo.emergencyContacts.relationship'
];

Apparently, the string 'personalInfo.emergencyContacts.relationship' cannot be directly used on application. We may need to use Array.split() to get an array of nested keys and then use Array.reduce() to traverse every level of keys in the object.

Solution

Step 1: Define the Function Structure

1
2
3
4
5
6
7
8
/**
* Deep toUpperCase in an object
* @param {any} obj the target object to conduct toUpper in object
* @param {string[]} fieldsList array of field names in string. Deep properties can be expressed as 'xxx.yy.zzzz'
*/
const toUpperInObject = function (obj, fieldsList) {
// Implementation
}

Step 2: Iterate the Field List and Get Their Nested Key Split

1
2
3
4
fieldsList.forEach(f => {
const propertyParts = f.split('.');
// The remaining implementation
});

If f is 'personalInfo.emergencyContacts.relationship', then propertyParts is ['personalInfo', 'emergencyContacts', 'relationship']

Step 3: Iterate the Object Keys to Find the Target

1
2
3
4
5
6
7
8
9
10
propertyParts.reduce((xs, x) => {
if(xs && xs[x]) {
if(typeof xs[x] === 'string') {
xs[x] = xs[x].toUpperCase();
}
return xs[x];
} else {
return null;
}
}, obj);

Make use of Array.reduce() function as the accumulated parameter can record the current object level, so that the target value can be accessed level by level.

For example, ['personalInfo', 'emergencyContacts', 'relationship'] will be operated as following code shows.

  • 1st iteration

x is 'personalInfo'

xs is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
countryApplied: 'Singapore',
personalInfo: {
name: 'Peter',
age: 24,
email: 'peter@tmail.com',
emergencyContacts: {
name: 'Ronald',
age: 49,
relationship: 'father',
email: 'ronald@gotmail.com'
},
}
}

xs[x] is

1
2
3
4
5
6
7
8
9
10
11
{
name: 'Peter',
age: 24,
email: 'peter@tmail.com',
emergencyContacts: {
name: 'Ronald',
age: 49,
relationship: 'father',
email: 'ronald@gotmail.com'
},
}
  • 2nd iteration

x is 'emergencyContacts'

xs is

1
2
3
4
5
6
7
8
9
10
11
{
name: 'Peter',
age: 24,
email: 'peter@tmail.com',
emergencyContacts: {
name: 'Ronald',
age: 49,
relationship: 'father',
email: 'ronald@gotmail.com'
},
}

xs[x] is

1
2
3
4
5
6
emergencyContacts: {
name: 'Ronald',
age: 49,
relationship: 'father',
email: 'ronald@gotmail.com'
},
  • 3rd iteration

x is 'relationship'

xs is

1
2
3
4
5
6
emergencyContacts: {
name: 'Ronald',
age: 49,
relationship: 'father',
email: 'ronald@gotmail.com'
},

xs[x] is 'father'

Now, we successfully located application.personalInfo.emergencyContacts.relationship, which is valued as 'father'. And then we make it uppercase using String.toUpperCase()

Using the same logic, every listed attribute will be accessed and necessary actions can be taken to the correct target.

Complete Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Deep toUpperCase in an object
* @param {any} obj the target object to conduct toUpper in object
* @param {string[]} fieldsList array of field names in string. Deep properties can be expressed as 'xxx.yy.zzzz'
*/
const toUpperInObject = function (obj, fieldsList) {
fieldsList.forEach(f => {
const propertyParts = f.split('.');
propertyParts.reduce((xs, x) => {
if(xs && xs[x]) {
if(typeof xs[x] === 'string') {
xs[x] = xs[x].toUpperCase();
}
return xs[x];
} else {
return null;
}
}, obj);
});
}

Another simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
a: {
b: 'test',
c: {
d: 'test',
e: 123
}
},
f: 'test'
};
const fieldsList = ['a.b', 'a.c.d'];
toUpperInObject(obj, fieldsList);

console.log(obj);

And then obj becomes

1
2
3
4
5
6
7
8
9
10
{
a: {
b: 'TEST',
c: {
d: 'TEST',
e: 123
}
},
f: 'test'
};