1

I want to write a function that takes a keyName, newValue, and object, and returns the object with the updated key/value pair. For example...

Given this data:

const data = {
  token: {
    id: "abcxyz",
    year: "2022"
  },
  order_data: {
    customer: "Jane",
    shipping: {
      country: "US",
      state: "TX"
    }
  }
}

and a function with these arguments:

const updateObject = (keyName, newValue, object) => {
...
}

I want to be able to call:

const newObject = updateObject("customer", "Bob", data);

so that

newObject = {
      token: {
        id: "abcxyz",
        year: "2022"
      },
      order_data: {
        customer: "Bob",
        shipping: {
          country: "US",
          state: "TX"
        }
      }
    }

My current wrong attempt looks like this:

const updateObject = (keyName, newVal, object) => {
  const results = {};
  for (var key in object) {
    if (key === keyName) {
      results = {
        ...object,
        keyName: newVal
      };
    } else {
      results[key] = object[key];
      if (typeof object[key] === "object") {
        updateObject(keyName, newVal, object.key);
      }
    }
  }
  return results
};

I've been digging through posts on recursion and spread operators all day but can't quite get it right. The nested object can be any shape and depth which is throwing me off.

JBrown
  • 825
  • 6
  • 22

4 Answers4

4

The spread syntax in your function isn't necessary (you already create a new object with results = {}), and keyname creates a .keyname property not one with a dynamic name (same problem in using object.key instead of object[key] - you might want to revisit dot vs bracket notation). But the main problem is that your updateObject function doesn't update the object it was passed, rather it returns a new one - and you need to consider that in your recursive call. So it should be

function updateObject(keyName, newVal, object) {
  const results = {};
  for (var key in object) {
    if (key === keyName) {
      results[key] = newVal;
    } else if (typeof object[key] === "object" && object[key] !== null) {
      results[key] = updateObject(keyName, newVal, object[key]);
    } else {
      results[key] = object[key];
    }
  }
  return results;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you! Very clear and helpful. I do need to read up on dot vs bracket notation. I just recently started coding in js and there is still much I need to learn. – JBrown Jul 21 '22 at 20:38
1

Its pretty simple. We just loop through and find the key and then update the value. if its not found we call the function again. Most important is to return the updated value at the end of the function

This line var newData = JSON.parse(JSON.stringify(data)); is just so that we dont edit the original object as you requested

var data = {
  token: {
    id: "abcxyz",
    year: "2022"
  },
  order_data: {
    customer: "Bob",
    shipping: {
      country: "US",
      state: "TX"
    }
  }
}

const updateObject = (keyName, newVal, object) => {
  for (var key in object) {
    if (key === keyName) {
      object[key] = newVal;
    } else {
      if(typeof object[key] === "object"){
        object[key] = updateObject(keyName, newVal, object[key]);
      }
    }
  }
  return object;
};

var newData = JSON.parse(JSON.stringify(data));
updateObject("customer", "Test", newData);
console.log(data);
console.log(newData);
Andam
  • 2,087
  • 1
  • 8
  • 21
1

If you're looking for an approach that doesn't mutate your original (and really now, who wouldn't be? ) we can do this by breaking our object into entries mapping them to the new value if the keys match and recurring on them otherwise, then putting the results back together. The recursion stops when passed a non-object, simply returning it unmodified.

It looks like this:

const updateObject = (keyName, newValue, object) => 
  Object (object) === object
    ? Object .fromEntries (Object .entries (object) .map (
        ([k, v]) => [k, k == keyName ? newValue : updateObject (keyName, newValue, v)]
      ))
    : object


const data = {token: {id: "abcxyz", year: "2022"}, order_data: {customer: "Jane", shipping: {country: "US", state: "TX"}}}

console .log ('Before:', data)
console .log ('customer = "Bob":', updateObject ('customer', 'Bob', data))
console .log ('state = "NY":', updateObject('state', 'NY', data))
console .log ('After (original data non mutatued):', data)
.as-console-wrapper {max-height: 100% !important; top: 0}

Do note, though, that there is a real concern, however this is implemented, that there could be similarly named keys in different parts of the data, and that any technique would update incorrect ones. You might look to tools that set this by path, with something like

setPath (['order_data', 'customer'], 'Bob', data)

or perhaps

setPath ('order_data.address.state', 'NY', data)
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Currently I know the shape of the data and that there will be no duplicate keys, but yes that is a good point. Could lead to some nasty bugs down the line. My problem is that I will not always know the path of the data I want to update. I will think on it and try to refactor. Thanks for the help! – JBrown Jul 22 '22 at 16:32
0

Here's an approach using mutation. We essentially make a new object in a wrapper function, and recurse until we find the property and change its value, then return the new object.

let data = {
  token: {
    id: 'abcxyz',
    year: '2022',
  },
  order_data: {
    customer: 'Bob',
    shipping: {
      country: 'US',
      state: 'TX',
    },
  },
};

function updateObject(key, newValue, obj) {
  let newObj = Object.assign({}, obj); // Make new object

  function updateKey(key, newValue, obj) {
    if (typeof obj !== 'object') return; // Basecase
    if (obj[key]) obj[key] = newValue;   // Look for and edit property
    else
      for (let prop in obj) {
        updateKey(key, newValue, obj[prop]); // Go deeper
      }
  }
  updateKey(key, newValue, newObj);
  return newObj;
}

console.log(updateObject('state', 'NY', data));
console.log(updateObject('customer', 'John', data));
console.log(updateObject('year', 2020, data));
Brother58697
  • 2,290
  • 2
  • 4
  • 12