Home > Enterprise >  Mapping an array of objects only works on a copy of the array
Mapping an array of objects only works on a copy of the array

Time:06-06

I have the feeling that this is an obscure side of Javascript that I simply do not know or understand.

I'm retrieving an array of objects from a database using Sequelize. It's a relatively complex array, as in contains associations. The array looks like this (it is simplified) :

[
  {
    name: '...',
    director: '...',
    genres: [
      {id: 1, name: 'Mystery'},
      {id: 2, name: 'Thriller'},
    ],
  },
  ...
]

I want to map this array so to change the format of genres from this:

genres: [
  {id: 1, name: 'Mystery'},
  {id: 2, name: 'Thriller'},
],

to this:

genres: ['Mystery', 'Thriller'],

So I did something which I believe is quite common in Javascript :

movies.map((movie) => {
  movie.genres = movie.genres.map(g => g.name);
  movie.actors = movie.actors.map(a => a.name);
  return movie;
});
console.log(JSON.stringify(movies));

However, once printed to the console, the array is completely unchanged. To have the desired effect on this array, I had to convert it into a string of characters and back into an array before performing the map() operations, like so:

movies = JSON.parse(JSON.stringify(movies));
movies.map((movie) => {
  movie.genres = movie.genres.map(g => g.name);
  movie.actors = movie.actors.map(a => a.name);
  return movie;
});
console.log(JSON.stringify(movies));

Why did it work ? I'm guessing that doing this conversion back and forth removed certain fields from the object, but I do not see how this could've had an effect on the array.

EDIT: using other methods like Array.from or the spread operator (movies = [...movies]) does not work.

CodePudding user response:

I am not familiar with Sequelize, but my best guess is that when you run JSON.parse and JSON.stringify, you create a copy of the value returned from Sequelize. When you create a copy, your code then "owns" the new object, thus allowing changes to be applied.

CodePudding user response:

If your optionsArray argument includes the 'IMMUTABLE' option then it's working as designed.

If you want to check it (not to solve anything but just to feel a little more sane) then you can probably verify immutability by seeing what Object.getOwnPropertyDescriptor(movies, 'genres') returns.

In which case, yes, creating a copy of the object is best. If you ascribe to functional programming, it's the "right" way to do it. Never changing objects but instead returning new ones is a fundamental practice of functional programming and avoids entire categories of programming errors.

sean-777 is right, you can clone it using several different libraries, or structuredClone(). In the case of your example, I'd probably just go with

function getName({name}) { return name; }
function mapName(a) {
  a.map(getName);
}
function convertMovie(movie) {
  return {
    ...movie,
    genres: mapName(movie.genres),
    actors: mapName(movie.actors)
  };
}
function convertMovies(movies) {
  return movies.map(convertMovie)
}
moviesConverted = convertMovies(movies);
console.log(JSON.stringify(moviesConverted));

CodePudding user response:

You can use structuredClone() method which creates a deep clone of a given value using the structured clone algorithm.

Demo :

const jsObj = [
  {
    name: 'alpha',
    director: 'beta',
    genres: [
      {id: 1, name: 'Mystery'},
      {id: 2, name: 'Thriller'},
    ],
  }
];

const clonedObj = structuredClone(jsObj);

const modifiedObj = clonedObj.map(obj => {
    obj.genres = obj.genres.map(genreObj => genreObj.name)
  return obj;
});

console.log(jsObj);
console.log(modifiedObj);

  • Related