Search code examples
javascriptarraysangularjsobjectarray-merge

How to merge these two objects in Angular JS?


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


Solution

  • 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