-2

I need help merging two objects in AngularJS :)

I'm using the trakt.tv API (http://docs.trakt.apiary.io/) to pull in history data. This returns a list of movies and episodes the user has watched (see: http://docs.trakt.apiary.io/#reference/sync/get-history)

As you can see it does not hold the users rating for a specific movie or episode. But there is a way to get all the users ratings for movies and tv shows ect.. (see http://docs.trakt.apiary.io/#reference/sync/get-ratings)

So what I want to do is match the users movie/episode ratings with the movie/episode in the history list, but I just can't wrap my head around how it should be done.

Example "History" object:

[
  {
    "id": 2008588422,
    "watched_at": "2016-05-17T10:36:12.000Z",
    "action": "watch",
    "type": "movie",
    "movie": {
      "title": "Batman v Superman: Dawn of Justice",
      "year": 2016,
      "ids": {
        "trakt": 129583,
        "slug": "batman-v-superman-dawn-of-justice-2016",
        "imdb": "tt2975590",
        "tmdb": 209112
      }
    }
  },
  {
    "id": 1995814508,
    "watched_at": "2016-05-09T22:39:47.000Z",
    "action": "checkin",
    "type": "movie",
    "movie": {
      "title": "Dirty Grandpa",
      "year": 2016,
      "ids": {
        "trakt": 188691,
        "slug": "dirty-grandpa-2016",
        "imdb": "tt1860213",
        "tmdb": 291870
      }
    }
  },
  {
    "id": 2005359787,
    "watched_at": "2016-05-09T01:00:00.000Z",
    "action": "watch",
    "type": "episode",
    "episode": {
      "season": 6,
      "number": 3,
      "title": "Oathbreaker",
      "ids": {
        "trakt": 1989021,
        "tvdb": 5579003,
        "imdb": "tt4131606",
        "tmdb": 1186952,
        "tvrage": 1065908650
      }
    },
    "show": {
      "title": "Game of Thrones",
      "year": 2011,
      "ids": {
        "trakt": 1390,
        "slug": "game-of-thrones",
        "tvdb": 121361,
        "imdb": "tt0944947",
        "tmdb": 1399,
        "tvrage": 24493
      }
    }
  }
]

Example "Ratings" object:

[
  {
    "rated_at": "2016-05-17T10:36:28.000Z",
    "rating": 7,
    "type": "movie",
    "movie": {
      "title": "Batman v Superman: Dawn of Justice",
      "year": 2016,
      "ids": {
        "trakt": 129583,
        "slug": "batman-v-superman-dawn-of-justice-2016",
        "imdb": "tt2975590",
        "tmdb": 209112
      }
    }
  },
  {
    "rated_at": "2016-04-05T15:55:36.000Z",
    "rating": 8,
    "type": "movie",
    "movie": {
      "title": "You Don't Mess With the Zohan",
      "year": 2008,
      "ids": {
        "trakt": 5835,
        "slug": "you-don-t-mess-with-the-zohan-2008",
        "imdb": "tt0960144",
        "tmdb": 10661
      }
    }
  },
  {
    "rated_at": "2016-05-24T16:19:54.000Z",
    "rating": 8,
    "type": "episode",
    "episode": {
      "season": 6,
      "number": 3,
      "title": "Oathbreaker",
      "ids": {
        "trakt": 1989021,
        "tvdb": 5579003,
        "imdb": "tt4131606",
        "tmdb": 1186952,
        "tvrage": 1065908650
      }
    },
    "show": {
      "title": "Game of Thrones",
      "year": 2011,
      "ids": {
        "trakt": 1390,
        "slug": "game-of-thrones",
        "tvdb": 121361,
        "imdb": "tt0944947",
        "tmdb": 1399,
        "tvrage": 24493
      }
    }
  }
]

Wanted result:

[
  {
    "id": 2008588422,
    "rated_at": "2016-05-17T10:36:28.000Z",
    "rating": 7,
    "watched_at": "2016-05-17T10:36:12.000Z",
    "action": "watch",
    "type": "movie",
    "movie": {
      "title": "Batman v Superman: Dawn of Justice",
      "year": 2016,
      "ids": {
        "trakt": 129583,
        "slug": "batman-v-superman-dawn-of-justice-2016",
        "imdb": "tt2975590",
        "tmdb": 209112
      }
    }
  },
  {
    "id": 1995814508,
    "watched_at": "2016-05-09T22:39:47.000Z",
    "action": "checkin",
    "type": "movie",
    "movie": {
      "title": "Dirty Grandpa",
      "year": 2016,
      "ids": {
        "trakt": 188691,
        "slug": "dirty-grandpa-2016",
        "imdb": "tt1860213",
        "tmdb": 291870
      }
    }
  },
  {
    "id": 2005359787,
    "rated_at": "2016-05-24T16:19:54.000Z",
    "rating": 8,
    "watched_at": "2016-05-09T01:00:00.000Z",
    "action": "watch",
    "type": "episode",
    "episode": {
      "season": 6,
      "number": 3,
      "title": "Oathbreaker",
      "ids": {
        "trakt": 1989021,
        "tvdb": 5579003,
        "imdb": "tt4131606",
        "tmdb": 1186952,
        "tvrage": 1065908650
      }
    },
    "show": {
      "title": "Game of Thrones",
      "year": 2011,
      "ids": {
        "trakt": 1390,
        "slug": "game-of-thrones",
        "tvdb": 121361,
        "imdb": "tt0944947",
        "tmdb": 1399,
        "tvrage": 24493
      }
    }
  }
]

Basically rating data should be added to the corresponding movies and episodes inside the history object.

An angular.merge or .extend doesn't give the desired result, and these seem to be very basic looking at the docs (https://docs.angularjs.org/api/ng/function/angular.merge)

All help welcome! :)

Thanks

webslash
  • 53
  • 1
  • 8
  • what does angular.merge give you? Would angular.extend do the job? – bmartin May 24 '16 at 16:46
  • Hi @bmartin angular.merge returns: http://pastebin.com/jkaDnfGS and angular.extend gives: http://pastebin.com/K3pZ9RJd both not the desired result :( – webslash May 24 '16 at 16:51
  • angular.merge looks correct to me? – bmartin May 24 '16 at 16:52
  • It's not, ratings should only be added to movies and episodes that exist in the History object. As you can see in the merge result ( pastebin.com/jkaDnfGS) the movie object for "Dirty Grandpa" became "You Don't Mess With the Zohan" (the rating data has simply overwritten the history object). – webslash May 24 '16 at 16:57
  • Also note this is just a sample, the real data will be a list of possibly hundreds of movies and episodes that have been rated, in random order. Same thing for the history data, in my sample "Batman v Superman: Dawn of Justice" is always the first movie but that might not be the case. I need some kind of merge that can recognize a specific movie (in the history object) and only if it has rating data for that specific movie, add that rating data correctly to the movie object. – webslash May 24 '16 at 17:01
  • Oh I didn't notice that - I see this is an array of objects, are you looping through it to merge each object? – bmartin May 24 '16 at 17:01
  • You will need to do some sort of sort to align the two lists, and place an empty object at each index that there is not a value in history that is in ratings. – bmartin May 24 '16 at 17:21
  • I'? m doing a merge of both objects, like this: angular.merge(historyData, ratingsData); I need something more advanced, I like your ideas but have no clue how to implement them :/ Did anyone ever do something similar, that could provide an example? – webslash May 24 '16 at 19:32
  • Sadly I have not - take a look at this however: http://stackoverflow.com/questions/32579066/merge-arrays-combining-matching-objects-in-angular-javascript – bmartin May 24 '16 at 19:48
  • What field in the ratings object do you use to reference your history object to merge? You can use a [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on your master array with [some](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/some) on the slave to find the specific object to merge. If you give me a field from one object i can use to reference the specific object in the other array i can give you an example. – ste2425 May 24 '16 at 22:24
  • @ste2425 Thanks for helping! movie.ids.trakt, show.ids.trakt or episode.ids.trakt will correspond in both the history and rating objects. (I know, the fact that there are movies, episodes and shows does complicate things even further) Looping over the history data, my script should look in the rating data for the trakt ID and type, to grab the user's rating for that item. – webslash May 25 '16 at 09:52

1 Answers1

1

After the op's comment where ES6 isn't an option ive created an ES5 equivalent with a find helper method to mimic ES6's find.

var merged = history.map(function (h) {
  var x = find(ratings, function (r) {
    return comparator(h, r);
  });

  return x ? angular.merge(h, x) : h;
});

/*
    Utility fn to get array.find style behaviour
*/
function find(arr, fn) {
  if (!Array.isArray(arr))
    return undefined;

  for(var i = 0; i < arr.length; i++){
    if (fn(arr[i]))
        return arr[i];
  }
}

fiddle


In my comment i said you could use ES5's array.some however i lied. some will return true should a single match meet your criteria (good for filters) however we want the actual object.

Luckily ES6 saves us yet again with find; (you could use filter in ES5 but this gives us an array back)

So with that something like this should be quite easy.

let merged = history.map(h => {
        let x =  ratings.find(r => comparator(r, h));
        return x ? angular.merge(h, x) : h;
    });

function comparator(review, history) {
    return review[review.type].ids.trakt === history[history.type].ids.trakt;
}

As always this is ES6 so IE might as well not turn up. You can run your ES6 through things like babel to transform into into ES5, or doe all the above with various for loops.

Fiddle

ste2425
  • 4,656
  • 2
  • 22
  • 37
  • Hi @ste2425, thanks for this detailed answer! This almost does what I want to accomplish, in the final outcome the object should have the history data with the rating data added to items when available. As you noted your outcome also includes a movie that has only been rated. But shouldn't show up in the outcome, because it is not in the users History. Also https://kangax.github.io/compat-table/es6/ shows me ES6 isn't supported on mobile devices (android). I'm building an Ionic (http://ionicframework.com/) mobile app so that's an issue. – webslash May 25 '16 at 11:00
  • Ahh so you want to flip, so history is the master and has rating info should it be available. That's quite easy. I would strongly advise trying to implement ES6 compliant code and using a tool such as babel to convert into ES5. Then once browsers catch up simply turn of the conversion. Ill edit the answer. – ste2425 May 25 '16 at 11:50
  • Yes that is correct! Ok, looking forward to your updated answer :) – webslash May 25 '16 at 11:59
  • @webslash Updated, hopefully will be of help – ste2425 May 25 '16 at 12:54
  • 1
    Absolutely brilliant, works as requested. Thank you very much @ste2425 ! – webslash May 25 '16 at 13:05