title: Combine the results of 2 API calls fetching different properties for the same objects with RxSwift
body: <p>I have a model called <strong>Track</strong>. It has a set of basic and a set of extended properties. List of tracks and their basic properties are fetched with a <strong>search</strong> API call, then I need to make another API call with those <strong>track IDs</strong> to fetch their extended properties.</p>
  
  <p><strong>The question is how to best combine the results</strong> of both API calls and populate the extended properties into the already created <strong>Track</strong> objects, and of course match them by ID (which unfortunately is a different property name in both calls' results). Note that there are many more keys returned in the real results sets - around 20-30 properties for each of the two calls.</p>
  
  <p><strong>Track.swift</strong></p>
  
  <pre><code>struct Track: Decodable {
  
  // MARK: - Basic properties
  
  let id: Int
  let title: String
  
  // MARK: - Extended properties
  
  let playbackURL: String
  
  enum CodingKeys: String, CodingKey {
      case id = "id"
  
      case title = "title"
  
      case playbackUrl = "playbackUrl"
  }
  
  init(from decoder: Decoder) throws {
      let container = try decoder.container(keyedBy: CodingKeys.self)
  
      let idString = try container.decode(String.self, forKey: CodingKeys.id)
      id = idString.int ?? 0
  
      title = try container.decode(String.self, forKey: CodingKeys.title)
  
      playbackURL = try container.decodeIfPresent(String.self, forKey: CodingKeys.playbackUrl) ?? ""
  }
  }
  </code></pre>
  
  <p><strong>ViewModel.swift</strong></p>
  
  <pre><code>let disposeBag = DisposeBag()
  var searchText = BehaviorRelay(value: "")
  private let provider = MoyaProvider&lt;MyAPI&gt;()
  let jsonResponseKeyPath = "results"
  
  public lazy var data: Driver&lt;[Track]&gt; = getData()
  
  private func searchTracks(query: String) -&gt; Observable&lt;[Track]&gt; {
      let decoder = JSONDecoder()
      return provider.rx.request(.search(query: query))
          .filterSuccessfulStatusCodes()
          .map([Track].self, atKeyPath: jsonResponseKeyPath, using: decoder, failsOnEmptyData: false)
          .asObservable()
  }
  
  private func getTracksMetadata(tracks: Array&lt;Track&gt;) -&gt; Observable&lt;[Track]&gt; {
      let trackIds: String = tracks.map( { $0.id.description } ).joined(separator: ",")
      let decoder = JSONDecoder()
      return provider.rx.request(.getTracksMetadata(trackIds: trackIds))
          .filterSuccessfulStatusCodes()
          .map({ result -&gt; [Track] in
  
          })
          .asObservable()
  }
  
  private func getData() -&gt; Driver&lt;[Track]&gt; {
      return self.searchText.asObservable()
          .throttle(0.3, scheduler: MainScheduler.instance)
          .distinctUntilChanged()
          .flatMapLatest(searchTracks)
          .flatMapLatest(getTracksMetadata)
          .asDriver(onErrorJustReturn: [])
  }
  </code></pre>
  
  <p><strong>The JSON result for .search API call is structured like this:</strong></p>
  
  <pre><code>{
    "results": [
      {
        "id": "4912",
        "trackid": 4912,
        "artistid": 1,
        "title": "Hello babe",
        "artistname": "Some artist name",
        "albumtitle": "The Best Of 1990-2000",
        "duration": 113
      },
      { 
        ...
      }
    ]
  }
  </code></pre>
  
  <p><strong>The JSON result for .getTracksMetadata API call is structured like this:</strong></p>
  
  <pre><code>[
    {
      "TrackID": "4912",
      "Title": "Hello babe",
      "Album": "The Best Of 1990-2000",
      "Artists": [
        {
          "ArtistID": "1",
          "ArtistName": "Some artist name"
        }
      ],
      "SomeOtherImportantMetadata1": "Something something 1",
      "SomeOtherImportantMetadata2": "Something something 2",
      "SomeOtherImportantMetadata3": "Something something 3"
    },
    { 
      ...
    }
  ]
  </code></pre>