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.
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.
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)
}
const moviesConverted = convertMovies(movies);
console.log(JSON.stringify(moviesConverted));
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);