Search code examples
javascripttypescriptdictionaryreduce

Manipulate object with reduce


Here's my input {field1: {field2:'123', field3:{field4:'123'}}}

The goal is {field1: {update: {field2:'123', field3: {update: {field4:'123'}}

Here's what I have tried

function updateDictHelper(obj) {
 Object.entries(obj).reduce((updateClause = {}, [key, value]) => {
  if (typeof value !== 'object') {
    return {... updateClause, [key]: value}
  } else {
    return {... updateClause, [key]: {update: updateDictHelper(value)}}
  }


 })
}

However, I can get it to work no matter what. I am quite new to java/typescript and would really appreciate any help.


Solution

  • You have two issues, the first is that your updateDirectHelper doesn't return anything. While you have a return inside of this function, it's actually nested inside of the reduce's callback function (updateClause, [key, value]) => {, not the updateDictHelper itself.

    Your other issue is how you're providing a default value for acc. Reduce natively supports an optional second argument which will act as the initial value for acc.

    reduce(callbackFn, initialValue)
    

    It is best practice to always provide one if you can. If you don't include it, .reduce() will essentially skip the first invocation of the callback and begin by calling the callback function with acc set to the first value in your array, and the second argument to the second value in your array. If your array only has one value, such as in your case, then that single value is what is returned from the .reduce() call, and your callback function for .reduce() is never invoked:

    const arrayWithOneElement = [1];
    const res = arrayWithOneElement.reduce(() => {
      console.log("I never run");
    }); // Notice no second argument
    console.log(res);

    To avoid all of this, you can instead pass a second argument as an empty object, which will be the starting value of acc:

    function updateDictHelper(obj) {
      return Object.entries(obj).reduce((updateClause, [key, value]) => {
        if (typeof value !== 'object') {
          return {...updateClause, [key]: value}
        } else {
          return {...updateClause, [key]: {update: updateDictHelper(value)}}
        }
      }, {});
    }
    
    console.log(updateDictHelper({field1: {field2:'123', field3:{field4:'123'}}}));

    I would personally do this with a newer method, Object.fromEntiries(), which will build the object for you. Saves you having to worry about dealing with .reduce() with this approach:

    function updateDictHelper(obj) {
      return Object.fromEntries(Object.entries(obj).map(([key, value]) => [
        key, 
        Object(value) === value ? {update: updateDictHelper(value)} : value
      ]));
    }
    
    console.log(updateDictHelper({field1: {field2:'123', field3:{field4:'123'}}}));