2

I am currently in a situation where there are certain properties in an object that I would like to change to snake case. At the moment I can change the names of properties in this object if they are not in a nested object or in an object in an array. This is my function along with the object I'm passing in:

/* an object to refer to where the values are what I want to change the name to */ const camelToSnake = { accountId: 'id', taxId: 'tax_id', individuals: 'customers', firstName: 'first_name', lastName: 'last_name', }; /* example object I am passing in to the function */ const obj = { accountId: "12345", individuals: [ {name: {firstName: "John", lastName: "Doe"}}, {name: {firstName: "Bob", lastName: "Smith"}}, ], taxId: "67890", } const restructureToSnakeCase = obj => { const newObj = {}; Object.keys(obj).forEach(key => { if(typeof obj[key] === 'object'){ restructureToSnakeCase(obj[key]) } if(Array.isArray(obj[key])){ obj[key].map(el => { restructureToSnakeCase(obj[key]) }) } newObj[camelToSnake[key] || key] = obj[key] }); return newObj; }; 

With my function so far, it is able to loop over any first level objects and change the property names, but isn't with any nested value.

With the object I pass in above this is the result:

{ id: "12345", customers: [ {name: {firstName: "John", lastName: "Doe"}}, {name: {firstName: "Bob", lastName: "Smith"}}, ], tax_id: "67890" } 

In this situation, "firstName" and "lastName" should be "first_name" and "last_name".

I thought with the recursion I have above, that it would be able to go into every nested object and change the property names accordingly, but evidently this is not working. I'd really appreciate any pointers.

2 Answers 2

3

You need to assign the results of recursively calling restructureToSnakeCase to their new keys on the newObj. You should also take care to check for null (whose typeof is obj), and check if the value is an array first (so that you can .map it - otherwise, if it's an object, just do a plain recursive call).

/* an object to refer to where the values are what I want to change the name to */ const camelToSnake = { accountId: 'id', taxId: 'tax_id', individuals: 'customers', firstName: 'first_name', lastName: 'last_name', }; /* example object I am passing in to the function */ const obj = { accountId: "12345", individuals: [ {name: {firstName: "John", lastName: "Doe"}}, {name: {firstName: "Bob", lastName: "Smith"}}, ], taxId: "67890", } const restructureToSnakeCase = obj => { if (typeof obj !== 'object' || obj === null) { return obj; } const newObj = {}; Object.entries(obj).forEach(([key, val]) => { const newKey = camelToSnake[key] || key; if (Array.isArray(val)) { newObj[newKey] = val.map(restructureToSnakeCase) } else if (typeof val === 'object' && val !== null) { newObj[newKey] = restructureToSnakeCase(val) } else { newObj[newKey] = val; } }); return newObj; }; console.log(restructureToSnakeCase(obj));

I would prefer to use Object.fromEntries, it's looks a lot cleaner:

/* an object to refer to where the values are what I want to change the name to */ const camelToSnake = { accountId: 'id', taxId: 'tax_id', individuals: 'customers', firstName: 'first_name', lastName: 'last_name', }; /* example object I am passing in to the function */ const obj = { accountId: "12345", individuals: [ {name: {firstName: "John", lastName: "Doe"}}, {name: {firstName: "Bob", lastName: "Smith"}}, ], taxId: "67890", } const restructureToSnakeCase = item => { if (typeof item !== 'object' || item === null) { return item; } if (Array.isArray(item)) { return item.map(restructureToSnakeCase); } return Object.fromEntries( Object.entries(item).map(([key, val]) => [ camelToSnake[key] || key, restructureToSnakeCase(val) ]) ); }; console.log(restructureToSnakeCase(obj));

Sign up to request clarification or add additional context in comments.

6 Comments

why did you introduce else if and else? you could have just sticked with two if's and retain the newObj[newKey] = val; and the end as it is in the OP's question
It makes the control flow clearer, and eliminates a bug - the typeof <someArray> will return object, so without the else if, it'll go through the array transformation and the object transformation
Super helpful, I previously did not know about the different behavior with two ifs vs an else if. Thanks!
@Antarktis When an answer solves your problem, you may consider marking it as Accepted (check the checkbox on the left) to indicate that the issue is resolved :)
@CertainPerformance I forgot to add that Object.fromEntries does not work for any version of Node below 12.0.0 (I am currently using 10.13).
|
1

Slight changes to your code, mentioned inline comments 1) to 4)
Basically have one local variable value, get the updated value and assign back to key.

const camelToSnake = { accountId: "id", taxId: "tax_id", individuals: "customers", firstName: "first_name", lastName: "last_name" }; const obj = { accountId: "12345", individuals: [ { name: { firstName: "John", lastName: "Doe" } }, { name: { firstName: "Bob", lastName: "Smith" } } ], taxId: "67890" }; const restructureToSnakeCase = obj => { const newObj = {}; Object.keys(obj).forEach(key => { let value = obj[key]; // 1. initialize value if (typeof obj[key] === "object") { value = restructureToSnakeCase(obj[key]); // 2. update value } if (Array.isArray(obj[key])) { value = obj[key].map(el => restructureToSnakeCase(el)); // 3. pass el and not obj[key] } newObj[camelToSnake[key] || key] = value; // 4. update value }); return newObj; }; console.log(restructureToSnakeCase(obj));

If you also want to generate snake case from camelCase, Check this.

function camelCaseTo_snake_case(str) { const [start, end] = ["A", "Z"]; const words = []; let word = ""; for (let i = 0; i < str.length; i++) { if (str[i] >= start && str[i] <= end) { if (word) { words.push(word); word = ""; } } word = `${word}${str[i]}`; } words.push(word); return words.map(word => word.toLowerCase()).join("_"); } console.log(camelCaseTo_snake_case("firstName"));

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.