Antarktis
Antarktis

Reputation: 163

Creating a new object by replacing property names found in another object

So there are plenty of questions that go over how to loop through an object and make a simple change to a property name, but what I'm trying to tackle is a tad trickier and would greatly appreciate some help.

Essentially, I want to make an object like this:

{
   home_number: '1234',
   customer: {
     name: {
       last_name: 'Smith',
     },
   },
};

turn into this

{
   home_number: '1234',
   individual: {
     name: {
       lastName: 'Smith',
     },
   },
};

At the moment, my function is as follows

function restructure(obj) {
  let newObj = {};
  const newKeys = {
    fed_tax_id: 'federalTaxId',
    company_structure: 'companyStructure',
    home_number: 'homeNumber',
    customer: 'individual',
    first_name: 'firstName',
    last_name: 'lastName',
  }
}
  for(let key in obj){
    if (typeof obj[key] === 'object') {
      restructure(obj[key])
    }
    if(Object.hasOwnProperty.call(newKeys, key)){
      let newProperty = newKeys[key]
      newObj[newProperty] = obj[key]
    }
    if(!Object.hasOwnProperty.call(newKeys, key)){
      newObj[key] = obj[key]
    }
return newObj;
  }

One thing I'm struggling with at the moment is removing the keys that need to be changed (I created the newKeys object as a way to show the keys that need to change and what they need to be changed to). I.e. in this case when customer is changed to individual, it will also change 'last_name' to 'lastName'.

With my function the way it is at the moment, the object comes back just as:

{ homeNumber: '1234' }

Thank you :) If this has been asked already please let me know, but after searching all over the place I wasn't able to find a question close enough to this.

Upvotes: 2

Views: 995

Answers (5)

Scott Sauyet
Scott Sauyet

Reputation: 50797

I would separate the key-transformation technique from the code that applies it to your object. In this snippet, we write a generic rekey function that takes a key-transformation function and returns a function that takes an object, running that function recursively over your keys.

So, if you want to transform all snake-case keys to camel-case ones, we can pass rekey a simple regex-based transformation function like this:

const rekey = (fn) => (obj) => 
  typeof obj == 'object'
    ? Object .fromEntries (
        Object .entries (obj) .map (([k, v]) => [fn(k), rekey (fn) (v)])
      )
    : obj


const snakeToCamel = (str) => 
  str .replace (/_([a-z])/g, (_, c) => c .toUpperCase ())

const obj = {home_number: '1234', customer: {name: {last_name: 'Smith'}}}

console .log (
  rekey (snakeToCamel) (obj)
)

If you're working in an environment without Object .fromEntries, it's easy to replace. You can use either of these versions:

// more elegant
const fromEntries = (pairs) => 
  pairs .reduce ((a, [k, v]) => ({... a, [k]: v}), {})

// more memory-efficient
const fromEntries = (pairs) => 
  pairs .reduce ((a, [k, v]) => {a[k] = v; return a}, {})

But the point of that separation is that this is now a reusable function, and if you want to change how you transform individual keys, say from a lookup, we can just pass rekey a different function:

const rekey = (fn) => (obj) => 
  typeof obj == 'object'
    ? Object .fromEntries (
        Object .entries (obj) .map (([k, v]) => [fn(k), rekey(fn)(v)])
      )
    : obj

const newKeys = {
    fed_tax_id: 'federalTaxId',
    company_structure: 'companyStructure',
    home_number: 'homeNumber',
    customer: 'individual',
    first_name: 'firstName',
    last_name: 'lastName',
}

const obj = {home_number: '1234', customer: {name: {last_name: 'Smith'}}}

console .log (
  rekey (key => newKeys[key] || key) (obj)
)

Of course with this curried function, you can store a reference to, say, rekey (snakeToCamel) and reuse it on many different objects.

Upvotes: 0

arizafar
arizafar

Reputation: 3122

You could use Object.entries and Object.fromEntries, Also, create newKeys object only once, rather creating it every time function gets invoked.

const newKeys = {
	fed_tax_id: 'federalTaxId',
	company_structure: 'companyStructure',
	home_number: 'homeNumber',
	customer: 'individual',
	first_name: 'firstName',
	last_name: 'lastName',
}
function restructure(obj) {

	let entries = Object.entries(obj).map(([key, val]) => {
		val = typeof val === 'object' && !Array.isArray(val) ? restructure(val) : val
		return [newKeys[key] || key, val]
	});

	return Object.fromEntries(entries)

}

console.log(restructure({
	home_number: '1234',
	customer: {
		name: {
			last_name: 'Smith',
		},
	},
}))

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386654

You need only a check for own property and the actual object, to prevent not own properties to be assigned.

For all properties, you could take first the new key or, if no value exists, as default the actual key.

Then you need to assign either the result of the recursive call or, if not an object just the value.

function restructure(obj) {
    let newObj = {};
    const newKeys = { fed_tax_id: 'federalTaxId', company_structure: 'companyStructure', home_number: 'homeNumber', customer: 'individual', first_name: 'firstName', last_name: 'lastName' };

    for (let key in obj) {
        if (!obj.hasOwnProperty(key)) continue;
        newObj[newKeys[key] || key] = obj[key] && typeof obj[key] === 'object'
            ? restructure(obj[key])
            : obj[key];
    }
    return newObj;
}

console.log(restructure({ home_number: '1234', customer: { name: { last_name: 'Smith' } } }));

Upvotes: 0

Maheer Ali
Maheer Ali

Reputation: 36574

You can use delete to remove property from object.

Note: My function will modify the original object. If you wanna copy the object make sure to make its deep copy using JSON methods

const newKeys = {
    fed_tax_id: 'federalTaxId',
    company_structure: 'companyStructure',
    home_number: 'homeNumber',
    customer: 'individual',
    first_name: 'firstName',
    last_name: 'lastName',
}
const obj = {
   home_number: '1234',
   individual: {
     name: {
       lastName: 'Smith',
     },
   },
};
function restructure(obj){
  for(let k in obj){
    if(typeof obj[k] === "object" && !Array.isArray(obj[k])){
      restructure(obj[k]);
    }
    if(newKeys[k]){
      obj[newKeys[k]] = obj[k];
      delete obj[k];
    }
  }
}

restructure(obj);
console.log(obj)

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370789

Since restructure returns the new object, you need to assign the result of recursively calling restructure, otherwise it'll go unused, which is what's happening in your current code.

But it would probably be easier to map an array of entries instead - replace the key in the entry with the associated value on the object if the object has that key, then turn the entries back into an object with Object.fromEntries:

const newKeys = {
  fed_tax_id: 'federalTaxId',
  company_structure: 'companyStructure',
  home_number: 'homeNumber',
  customer: 'individual',
  first_name: 'firstName',
  last_name: 'lastName',
};
const restructure = obj => Object.fromEntries(
  Object.entries(obj).map(
    ([key, val]) => [
      newKeys[key] || key,
      typeof val === 'object' && val !== null ? restructure(val) : val
    ]
  )
);

console.log(restructure({
   home_number: '1234',
   customer: {
     name: {
       last_name: 'Smith',
     },
   },
}));

Keep in mind that typeof null gives object, so you'll want to check for null before recursively restructuring (as done in the above code), else you might occasionally run into errors.

Upvotes: 1

Related Questions