-3

I'm trying to manipulate an array like this:

data = [
   {
      "id":"1",
      "items":[
         {
            "title":"item 1"
         },
         {
            "title":"item 2"
         }
      ]
   },
   {
      "id":"2",
      "items":[
         {
            "title":"item2 1"
         },
         {
            "title":"item2 2"
         }
      ]
   }
]

I need, for example, to push another array:

      [
         {
            "title":"item new 1"
         },
         {
            "title":"item new 2"
         }
      ]

inside data[0].items and obtain:

data = [
       {
          "id":"1",
          "items":[
             {
            "title":"item new 1"
         },
         {
            "title":"item new 2"
         }
          ]
       },
       {
          "id":"2",
          "items":[
             {
                "title":"item2 1"
             },
             {
                "title":"item2 2"
             }
          ]
       }
    ]

...how can I do this maintaining immutability, for example with Lodash? Not understand anding how to change only a specific sub object in a data structure. Somebody have suggestions?

Thanks

WolF
  • 51
  • 1
  • 11
  • `data[0].items = [{ "title": "item new 1" }, { "title": "item new 2" }]` – jabaa May 01 '22 at 13:00
  • Sorry, No. I know how to access to the correct obj (es. `data[0].items = []; data[0].items.push(newArray)`). My problem is that I need to preserve immutability, that's why I'm searching a way with Lodash, thanks – WolF May 01 '22 at 13:02
  • What do you mean with _"preserve immutability"_? There is no immutability in your code. How do you want to modify an immutable object? – jabaa May 01 '22 at 13:14
  • Are you looking for [`Lodash#cloneDeep`](https://lodash.com/docs/#cloneDeep) – jabaa May 01 '22 at 13:23
  • I voted to close because the question is unclear. – Thomas Sablik May 01 '22 at 13:29
  • from the question: _**maintaining immutability**_ --> OP (@WolF), this typically means that the original object or array being manipulated must remain as-is and changes are made on a copy of the original. Is this how you intended it? So, when you immutably add some new information to an existing object/array, you get a new object/array (while the original remains as-is / not-mutated). – jsN00b May 01 '22 at 13:33
  • 3
    @jsN00b Immutability usually means that changes are ignored (e.g. string: `let s = 'abc'; s[1] = 'd';`) or cause an error (e.g. [`Object.freeze`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)). It has nothing to do with deep copies or pure functions. – jabaa May 01 '22 at 13:41

3 Answers3

-1

Presented below is one possible way to immutably add given array into a particular index "items" prop.

Code Snippet

const immutableAdd = (aIdx, addThis, orig) => {
  const newData = structuredClone(orig);
  newData[aIdx].items = addThis;
  return newData;
};

const data = [{
    "id": "1",
    "items": [{
        "title": "item 1"
      },
      {
        "title": "item 2"
      }
    ]
  },
  {
    "id": "2",
    "items": [{
        "title": "item2 1"
      },
      {
        "title": "item2 2"
      }
    ]
  }
];

const addThisArr = [{
    "title": "item new 1"
  },
  {
    "title": "item new 2"
  }
];

console.log('immutableAdd result: ', immutableAdd(0, addThisArr, data));
console.log('original data: ', data);
.as-console-wrapper { max-height: 100% !important; top: 0 }

Explanation

  • Use structuredClone() to deep-clone existing data array
  • Navigate to the aIdx of the cloned-array
  • Assign the given array (to be added) into aIdx's items prop

NOTE

This solution does not use lodash as it is not mandatory (to use lodash) to perform immutable operations.

jsN00b
  • 3,584
  • 2
  • 8
  • 21
  • 1
    _"immutably add"_ is an oxymoron. Either it's immutable or you can add something. – jabaa May 01 '22 at 13:56
  • @jabaa insofar as reallocating memory for the array `data` goes, the principle underlying `immutability` is held by not altering the array. – jsN00b May 01 '22 at 14:16
-2

If you want to maintain the immutability of original data, just map the content of original data to the new data as you want, and wrap your logic into a pure function to improve readability.

const dataOriginal = [{
    "id": "1",
    "items": [{
        "title": "item 1"
      },
      {
        "title": "item 2"
      }
    ]
  },
  {
    "id": "2",
    "items": [{
        "title": "item2 1"
      },
      {
        "title": "item2 2"
      }
    ]
  }
]

const dataNew = createDataWithSomethingNew(dataOriginal, [{
    "title": "item new 1"
  },
  {
    "title": "item new 2"
  }
])

function createDataWithSomethingNew(data, props) {
  return data.map(function changeItemsOfId1ToProps(value) {
    if (value.id === '1') {
      return {
        id: value.id,
        items: props
      }
    } else {
      return value
    }
  })
}
kwh
  • 32
  • 4
-2

lodash has a method _.update can modify object with the correct path in string provided.

Another method _.cloneDeep can copy you object deeply. So that change in the pre-copied object will not affect the cloned object.

Finally use a deep freeze function to call Object.freeze recursively on the cloned object to make it immutable.

var data = [
   {
      "id":"1",
      "items":[
         {
            "title":"item 1"
         },
         {
            "title":"item 2"
         }
      ]
   },
   {
      "id":"2",
      "items":[
         {
            "title":"item2 1"
         },
         {
            "title":"item2 2"
         }
      ]
   }
]

var clonedData = _.cloneDeep(data) // copy the full object to avoid modify the source data

// update the data of that path '[0].items' in clonedData
_.update(clonedData, '[0].items', function(n) {
  return [
    {
      "title":"item new 1"
    },
    {
      "title":"item new 2"
    }
  ]
})

// provide object immutability
 const deepFreeze = (obj1) => {
    _.keys(obj1).forEach((property) => {
      if (
        typeof obj1[property] === "object" &&
        !Object.isFrozen(obj1[property])
      )
        deepFreeze(obj1[property])
    });
    Object.freeze(obj1)
  };

deepFreeze(clonedData)


data[2] = {id: 3} // data will be changed
data[1].items[2] = {title: "3"} // and also this one

clonedData[2] = {id: 3} // nothing will be changed
clonedData[1].items[2] = {title: "3"} // and also this one

console.log(`data:`, data);
console.log(`clonedData:`, clonedData);

Runkit Example

Edwin
  • 395
  • 2
  • 12
  • 1
    _"Another method _.cloneDeep can provided you a deep level immutable object."_ `_.cloneDeep` doesn't provide an immutable object. – jabaa May 01 '22 at 13:52
  • @jabaa Thanks for pointing the out. I never know that I was doing it wrong all the time learned from some YouTube tutorial. Sadly this one is closed and being down voted, but at least I have learned something. :) – Edwin May 01 '22 at 16:05
  • You've learned from a video that `_.cloneDeep` returns an immutable object? Do you have a link? – jabaa May 01 '22 at 16:09
  • IIRC, it was a `Redux` tutorial, but I can remember exactly which one. It taught that `spread operator` on an array or object can make it immutable. That's why I wrongly believe that cloneDeep can make object immutable. – Edwin May 01 '22 at 16:54
  • Spread operator can't make an object immutable. – jabaa May 01 '22 at 17:10