Combine the results of 2 API calls fetching different properties for the same objects with RxSwift












0















I have a model called Track. It has a set of basic and a set of extended properties. List of tracks and their basic properties are fetched with a search API call, then I need to make another API call with those track IDs to fetch their extended properties.



The question is how to best combine the results of both API calls and populate the extended properties into the already created Track 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.



Track.swift



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) ?? ""
}
}


ViewModel.swift



let disposeBag = DisposeBag()
var searchText = BehaviorRelay(value: "")
private let provider = MoyaProvider<MyAPI>()
let jsonResponseKeyPath = "results"

public lazy var data: Driver<[Track]> = getData()

private func searchTracks(query: String) -> Observable<[Track]> {
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<Track>) -> Observable<[Track]> {
let trackIds: String = tracks.map( { $0.id.description } ).joined(separator: ",")
let decoder = JSONDecoder()
return provider.rx.request(.getTracksMetadata(trackIds: trackIds))
.filterSuccessfulStatusCodes()
.map({ result -> [Track] in

})
.asObservable()
}

private func getData() -> Driver<[Track]> {
return self.searchText.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest(searchTracks)
.flatMapLatest(getTracksMetadata)
.asDriver(onErrorJustReturn: )
}


The JSON result for .search API call is structured like this:



{
"results": [
{
"id": "4912",
"trackid": 4912,
"artistid": 1,
"title": "Hello babe",
"artistname": "Some artist name",
"albumtitle": "The Best Of 1990-2000",
"duration": 113
},
{
...
}
]
}


The JSON result for .getTracksMetadata API call is structured like this:



[
{
"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"
},
{
...
}
]









share|improve this question

























  • This might be what you need reactivex.io/documentation/operators/combinelatest.html

    – Bohdan Savych
    Nov 16 '18 at 10:27











  • What does the JSON look like for the two calls?

    – Daniel T.
    Nov 16 '18 at 19:33











  • JSON results added to original question. The result set is much simplified, the result normally contains 20-30 properties for each object.

    – Blackbeard
    Nov 19 '18 at 10:17


















0















I have a model called Track. It has a set of basic and a set of extended properties. List of tracks and their basic properties are fetched with a search API call, then I need to make another API call with those track IDs to fetch their extended properties.



The question is how to best combine the results of both API calls and populate the extended properties into the already created Track 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.



Track.swift



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) ?? ""
}
}


ViewModel.swift



let disposeBag = DisposeBag()
var searchText = BehaviorRelay(value: "")
private let provider = MoyaProvider<MyAPI>()
let jsonResponseKeyPath = "results"

public lazy var data: Driver<[Track]> = getData()

private func searchTracks(query: String) -> Observable<[Track]> {
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<Track>) -> Observable<[Track]> {
let trackIds: String = tracks.map( { $0.id.description } ).joined(separator: ",")
let decoder = JSONDecoder()
return provider.rx.request(.getTracksMetadata(trackIds: trackIds))
.filterSuccessfulStatusCodes()
.map({ result -> [Track] in

})
.asObservable()
}

private func getData() -> Driver<[Track]> {
return self.searchText.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest(searchTracks)
.flatMapLatest(getTracksMetadata)
.asDriver(onErrorJustReturn: )
}


The JSON result for .search API call is structured like this:



{
"results": [
{
"id": "4912",
"trackid": 4912,
"artistid": 1,
"title": "Hello babe",
"artistname": "Some artist name",
"albumtitle": "The Best Of 1990-2000",
"duration": 113
},
{
...
}
]
}


The JSON result for .getTracksMetadata API call is structured like this:



[
{
"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"
},
{
...
}
]









share|improve this question

























  • This might be what you need reactivex.io/documentation/operators/combinelatest.html

    – Bohdan Savych
    Nov 16 '18 at 10:27











  • What does the JSON look like for the two calls?

    – Daniel T.
    Nov 16 '18 at 19:33











  • JSON results added to original question. The result set is much simplified, the result normally contains 20-30 properties for each object.

    – Blackbeard
    Nov 19 '18 at 10:17
















0












0








0








I have a model called Track. It has a set of basic and a set of extended properties. List of tracks and their basic properties are fetched with a search API call, then I need to make another API call with those track IDs to fetch their extended properties.



The question is how to best combine the results of both API calls and populate the extended properties into the already created Track 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.



Track.swift



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) ?? ""
}
}


ViewModel.swift



let disposeBag = DisposeBag()
var searchText = BehaviorRelay(value: "")
private let provider = MoyaProvider<MyAPI>()
let jsonResponseKeyPath = "results"

public lazy var data: Driver<[Track]> = getData()

private func searchTracks(query: String) -> Observable<[Track]> {
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<Track>) -> Observable<[Track]> {
let trackIds: String = tracks.map( { $0.id.description } ).joined(separator: ",")
let decoder = JSONDecoder()
return provider.rx.request(.getTracksMetadata(trackIds: trackIds))
.filterSuccessfulStatusCodes()
.map({ result -> [Track] in

})
.asObservable()
}

private func getData() -> Driver<[Track]> {
return self.searchText.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest(searchTracks)
.flatMapLatest(getTracksMetadata)
.asDriver(onErrorJustReturn: )
}


The JSON result for .search API call is structured like this:



{
"results": [
{
"id": "4912",
"trackid": 4912,
"artistid": 1,
"title": "Hello babe",
"artistname": "Some artist name",
"albumtitle": "The Best Of 1990-2000",
"duration": 113
},
{
...
}
]
}


The JSON result for .getTracksMetadata API call is structured like this:



[
{
"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"
},
{
...
}
]









share|improve this question
















I have a model called Track. It has a set of basic and a set of extended properties. List of tracks and their basic properties are fetched with a search API call, then I need to make another API call with those track IDs to fetch their extended properties.



The question is how to best combine the results of both API calls and populate the extended properties into the already created Track 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.



Track.swift



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) ?? ""
}
}


ViewModel.swift



let disposeBag = DisposeBag()
var searchText = BehaviorRelay(value: "")
private let provider = MoyaProvider<MyAPI>()
let jsonResponseKeyPath = "results"

public lazy var data: Driver<[Track]> = getData()

private func searchTracks(query: String) -> Observable<[Track]> {
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<Track>) -> Observable<[Track]> {
let trackIds: String = tracks.map( { $0.id.description } ).joined(separator: ",")
let decoder = JSONDecoder()
return provider.rx.request(.getTracksMetadata(trackIds: trackIds))
.filterSuccessfulStatusCodes()
.map({ result -> [Track] in

})
.asObservable()
}

private func getData() -> Driver<[Track]> {
return self.searchText.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest(searchTracks)
.flatMapLatest(getTracksMetadata)
.asDriver(onErrorJustReturn: )
}


The JSON result for .search API call is structured like this:



{
"results": [
{
"id": "4912",
"trackid": 4912,
"artistid": 1,
"title": "Hello babe",
"artistname": "Some artist name",
"albumtitle": "The Best Of 1990-2000",
"duration": 113
},
{
...
}
]
}


The JSON result for .getTracksMetadata API call is structured like this:



[
{
"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"
},
{
...
}
]






ios swift rx-swift moya






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 19 '18 at 10:17







Blackbeard

















asked Nov 16 '18 at 9:19









BlackbeardBlackbeard

3191417




3191417













  • This might be what you need reactivex.io/documentation/operators/combinelatest.html

    – Bohdan Savych
    Nov 16 '18 at 10:27











  • What does the JSON look like for the two calls?

    – Daniel T.
    Nov 16 '18 at 19:33











  • JSON results added to original question. The result set is much simplified, the result normally contains 20-30 properties for each object.

    – Blackbeard
    Nov 19 '18 at 10:17





















  • This might be what you need reactivex.io/documentation/operators/combinelatest.html

    – Bohdan Savych
    Nov 16 '18 at 10:27











  • What does the JSON look like for the two calls?

    – Daniel T.
    Nov 16 '18 at 19:33











  • JSON results added to original question. The result set is much simplified, the result normally contains 20-30 properties for each object.

    – Blackbeard
    Nov 19 '18 at 10:17



















This might be what you need reactivex.io/documentation/operators/combinelatest.html

– Bohdan Savych
Nov 16 '18 at 10:27





This might be what you need reactivex.io/documentation/operators/combinelatest.html

– Bohdan Savych
Nov 16 '18 at 10:27













What does the JSON look like for the two calls?

– Daniel T.
Nov 16 '18 at 19:33





What does the JSON look like for the two calls?

– Daniel T.
Nov 16 '18 at 19:33













JSON results added to original question. The result set is much simplified, the result normally contains 20-30 properties for each object.

– Blackbeard
Nov 19 '18 at 10:17







JSON results added to original question. The result set is much simplified, the result normally contains 20-30 properties for each object.

– Blackbeard
Nov 19 '18 at 10:17














1 Answer
1






active

oldest

votes


















2














The solution here is a two phase approach. First you should define two different structs for the two network calls and a third struct for the combined result. Let's say you go with:



struct TrackBasic {
let id: Int
let title: String
}

struct TrackMetadata {
let id: Int // or whatever it's called.
let playbackURL: String
}

struct Track {
let id: Int
let title: String
let playbackURL: String
}


And define your functions like so:



func searchTracks(query: String) -> Observable<[TrackBasic]>
func getTracksMetadata(tracks: [Int]) -> Observable<[TrackMetadata]>


Now you can make the two calls and wrap the data from the two separate endpoints into the combined struct:



searchText
.flatMapLatest { searchTracks(query: $0) }
.flatMapLatest { basicTracks in
Observable.combineLatest(Observable.just(basicTracks), getTracksMetadata(tracks: basicTracks.map { $0.id }))
}
.map { zip($0.0, $0.1) }
.map { $0.map { Track(id: $0.0.id, title: $0.0.title, playbackURL: $0.1.playbackURL) } }


The above assumes that the track metadata comes in the same order that it was requested in. If that is not the case then the last map will have to be more complex.






share|improve this answer


























  • The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

    – Daniel T.
    Nov 16 '18 at 19:56













  • The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

    – Blackbeard
    Nov 19 '18 at 10:22








  • 1





    That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

    – Daniel T.
    Nov 19 '18 at 13:08














Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53334778%2fcombine-the-results-of-2-api-calls-fetching-different-properties-for-the-same-ob%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









2














The solution here is a two phase approach. First you should define two different structs for the two network calls and a third struct for the combined result. Let's say you go with:



struct TrackBasic {
let id: Int
let title: String
}

struct TrackMetadata {
let id: Int // or whatever it's called.
let playbackURL: String
}

struct Track {
let id: Int
let title: String
let playbackURL: String
}


And define your functions like so:



func searchTracks(query: String) -> Observable<[TrackBasic]>
func getTracksMetadata(tracks: [Int]) -> Observable<[TrackMetadata]>


Now you can make the two calls and wrap the data from the two separate endpoints into the combined struct:



searchText
.flatMapLatest { searchTracks(query: $0) }
.flatMapLatest { basicTracks in
Observable.combineLatest(Observable.just(basicTracks), getTracksMetadata(tracks: basicTracks.map { $0.id }))
}
.map { zip($0.0, $0.1) }
.map { $0.map { Track(id: $0.0.id, title: $0.0.title, playbackURL: $0.1.playbackURL) } }


The above assumes that the track metadata comes in the same order that it was requested in. If that is not the case then the last map will have to be more complex.






share|improve this answer


























  • The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

    – Daniel T.
    Nov 16 '18 at 19:56













  • The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

    – Blackbeard
    Nov 19 '18 at 10:22








  • 1





    That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

    – Daniel T.
    Nov 19 '18 at 13:08


















2














The solution here is a two phase approach. First you should define two different structs for the two network calls and a third struct for the combined result. Let's say you go with:



struct TrackBasic {
let id: Int
let title: String
}

struct TrackMetadata {
let id: Int // or whatever it's called.
let playbackURL: String
}

struct Track {
let id: Int
let title: String
let playbackURL: String
}


And define your functions like so:



func searchTracks(query: String) -> Observable<[TrackBasic]>
func getTracksMetadata(tracks: [Int]) -> Observable<[TrackMetadata]>


Now you can make the two calls and wrap the data from the two separate endpoints into the combined struct:



searchText
.flatMapLatest { searchTracks(query: $0) }
.flatMapLatest { basicTracks in
Observable.combineLatest(Observable.just(basicTracks), getTracksMetadata(tracks: basicTracks.map { $0.id }))
}
.map { zip($0.0, $0.1) }
.map { $0.map { Track(id: $0.0.id, title: $0.0.title, playbackURL: $0.1.playbackURL) } }


The above assumes that the track metadata comes in the same order that it was requested in. If that is not the case then the last map will have to be more complex.






share|improve this answer


























  • The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

    – Daniel T.
    Nov 16 '18 at 19:56













  • The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

    – Blackbeard
    Nov 19 '18 at 10:22








  • 1





    That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

    – Daniel T.
    Nov 19 '18 at 13:08
















2












2








2







The solution here is a two phase approach. First you should define two different structs for the two network calls and a third struct for the combined result. Let's say you go with:



struct TrackBasic {
let id: Int
let title: String
}

struct TrackMetadata {
let id: Int // or whatever it's called.
let playbackURL: String
}

struct Track {
let id: Int
let title: String
let playbackURL: String
}


And define your functions like so:



func searchTracks(query: String) -> Observable<[TrackBasic]>
func getTracksMetadata(tracks: [Int]) -> Observable<[TrackMetadata]>


Now you can make the two calls and wrap the data from the two separate endpoints into the combined struct:



searchText
.flatMapLatest { searchTracks(query: $0) }
.flatMapLatest { basicTracks in
Observable.combineLatest(Observable.just(basicTracks), getTracksMetadata(tracks: basicTracks.map { $0.id }))
}
.map { zip($0.0, $0.1) }
.map { $0.map { Track(id: $0.0.id, title: $0.0.title, playbackURL: $0.1.playbackURL) } }


The above assumes that the track metadata comes in the same order that it was requested in. If that is not the case then the last map will have to be more complex.






share|improve this answer















The solution here is a two phase approach. First you should define two different structs for the two network calls and a third struct for the combined result. Let's say you go with:



struct TrackBasic {
let id: Int
let title: String
}

struct TrackMetadata {
let id: Int // or whatever it's called.
let playbackURL: String
}

struct Track {
let id: Int
let title: String
let playbackURL: String
}


And define your functions like so:



func searchTracks(query: String) -> Observable<[TrackBasic]>
func getTracksMetadata(tracks: [Int]) -> Observable<[TrackMetadata]>


Now you can make the two calls and wrap the data from the two separate endpoints into the combined struct:



searchText
.flatMapLatest { searchTracks(query: $0) }
.flatMapLatest { basicTracks in
Observable.combineLatest(Observable.just(basicTracks), getTracksMetadata(tracks: basicTracks.map { $0.id }))
}
.map { zip($0.0, $0.1) }
.map { $0.map { Track(id: $0.0.id, title: $0.0.title, playbackURL: $0.1.playbackURL) } }


The above assumes that the track metadata comes in the same order that it was requested in. If that is not the case then the last map will have to be more complex.







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 16 '18 at 19:58

























answered Nov 16 '18 at 19:53









Daniel T.Daniel T.

14.5k22734




14.5k22734













  • The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

    – Daniel T.
    Nov 16 '18 at 19:56













  • The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

    – Blackbeard
    Nov 19 '18 at 10:22








  • 1





    That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

    – Daniel T.
    Nov 19 '18 at 13:08





















  • The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

    – Daniel T.
    Nov 16 '18 at 19:56













  • The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

    – Blackbeard
    Nov 19 '18 at 10:22








  • 1





    That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

    – Daniel T.
    Nov 19 '18 at 13:08



















The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

– Daniel T.
Nov 16 '18 at 19:56







The key here is to use flatMap, combineLatest, and just to carry over the old data along with the new data. This and other interesting ways of combining observables can be found here: medium.com/@danielt1263/…

– Daniel T.
Nov 16 '18 at 19:56















The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

– Blackbeard
Nov 19 '18 at 10:22







The thing is that I cannot assume that the order is the same for both API calls. Also I need to compare single results in each result array by different properties (if obj.id == otherobj.trackid) - see the newly added JSON result set in original question. And third, there are many many properties in the actual production result set, so describing each property in the Track() initializer would be cumbersome.

– Blackbeard
Nov 19 '18 at 10:22






1




1





That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

– Daniel T.
Nov 19 '18 at 13:08







That's fine, just sort them before passing to the zip .map { zip($0.sorted(by: { $0.id < $1.id }), $1.sorted(by: { $0.trackID < $1.trackID })) } and make the Track struct simpler by giving it only two properties struct Track { let basic: TrackBasic; let meta: TrackMetadata }. The basic advice is still the same, use flatMap, combineLatest and just.

– Daniel T.
Nov 19 '18 at 13:08






















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53334778%2fcombine-the-results-of-2-api-calls-fetching-different-properties-for-the-same-ob%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Xamarin.iOS Cant Deploy on Iphone

Glorious Revolution

Dulmage-Mendelsohn matrix decomposition in Python