Automatic parsing JSON with Swift 5 and Alomofire - json

I am calling an API via Alomofire in Swift 5 and get the following Result Set in JSON format :
Here is the call :
AF.request("https://shopping.yahooapis.jp/ShoppingWebService/V1/json/itemSearch", parameters: parameters).responseJSON { response in
print(response.result)
}
I have the following info in the result : Content-Encoding: gzip, the solution may come from here)
Here is the JSON Response body :
{"ResultSet":{"totalResultsAvailable":"3","totalResultsReturned":3,"firstResultPosition":"1","0":{"Result":{"Request":{"Query":""},"Modules":"","0":{"Name":"\u3010\u30ad\u30e3\u30c3\u30b7\u30e5\u30ec\u30b95\uff05\u9084\u5143\u3011\u30ab\u30eb\u30d3\u30fc\u3000\u304b\u3063\u3071\u3048\u3073\u305b\u3093\u7d00\u5dde\u306e\u6885\u547370g\u3000\u3010\u30a4\u30fc\u30b8\u30e3\u30d1\u30f3\u30e2\u30fc\u30eb\u3011","Description":"\u3010\u691c\u7d22\u30ad\u30fc\u30ef\u30fc\u30c9\uff08\u5546\u54c1\u5185\u5bb9\u3092\u4fdd\u969c\u3059\u308b\u3082\u306e\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff09\u3011\u304a\u83d3\u5b50\u3000\u304a\u3084\u3064\u3000\u30b9\u30ca\u30c3\u30af\u3000\u30b9\u30ca\u30c3\u30af\u83d3\u5b50\u3000\u30ab\u30c3\u30d1\u30a8\u30d3\u30bb\u30f3\u3000\u3046\u3081\u5473\u3000\u30a6\u30e1\u3000\u6d77\u8001\u3000\u30ab\u30eb\u30b7\u30a6\u30e0","Headline":"","Url":"https:\/\/store.shopping.yahoo.co.jp\/ejapan\/4901330197711f.html","ReleaseDate":"","Availability":"instock","Code":"ejapan_4901330197711f","Condition":"new","Image":{"Id":"ejapan_4901330197711f","Small":"https:\/\/item-shopping.c.yimg.jp\/i\/c\/ejapan_4901330197711f","Medium":"https:\/\/item-shopping.c.yimg.jp\/i\/g\/ejapan_4901330197711f"},"Review":{"Rate":"0.00","Count":"0","Url":"https:\/\/shopping.yahoo.co.jp\/review\/item\/list?store_id=ejapan&page_key=4901330197711f"},"Affiliate":{"Rate":"1.0"},"Price":{"_attributes":{"currency":"JPY"},"_value":"121"},"PremiumPrice":"","PriceLabel":{"_attributes":{"taxIncluded":"true"},"FixedPrice":"","DefaultPrice":"121","SalePrice":"","PremiumPriceStatus":"0","PremiumPrice":"121","PremiumDiscountType":"","PremiumDiscountRate":"","PeriodStart":"","PeriodEnd":""},"Point":{"Amount":"1","Times":"1","PremiumAmount":"1","PremiumTimes":"1"},"Shipping":{"Code":"1","Name":"\u8a2d\u5b9a\u7121\u3057"},"Category":{"Current":{"Id":"13451","Name":"\u305d\u306e\u4ed6\u30b9\u30ca\u30c3\u30af\u3001\u304a\u83d3\u5b50\u3001\u304a\u3064\u307e\u307f"}},"CategoryIdPath":{"0":{"Id":"1"},"1":{"Id":"2498"},"2":{"Id":"4745"},"3":{"Id":"13451"},"_container":"Category"},"Brands":{"Name":"\u30ab\u30eb\u30d3\u30fc","Path":{"0":{"Id":"1"},"1":{"Id":"16267"},"_container":"Brand"}},"JanCode":"4901330197711","Model":"","IsbnCode":"","Store":{"Id":"ejapan","Name":"e\u30b8\u30e3\u30d1\u30f3","Url":"https:\/\/store.shopping.yahoo.co.jp\/ejapan\/","Payment":{"0":{"Code":"1","Name":"\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9"},"1":{"Code":"16","Name":"Yahoo!\u30a6\u30a9\u30ec\u30c3\u30c8\u306b\u767b\u9332\u3057\u3066\u3044\u308b\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9"},"2":{"Code":"4","Name":"\u5546\u54c1\u4ee3\u5f15"},"3":{"Code":"2","Name":"\u9280\u884c\u632f\u8fbc"},"4":{"Code":"8","Name":"\u90f5\u4fbf\u632f\u66ff"},"5":{"Code":"1024","Name":"\u30bd\u30d5\u30c8\u30d0\u30f3\u30af\u307e\u3068\u3081\u3066\u652f\u6255\u3044"},"6":{"Code":"4096","Name":"PayPay"},"_container":"Method"},"IsBestStore":"false","Ratings":{"Rate":"4.5","Count":"11871","Total":"51092","DetailRate":"4.3"},"Image":{"Id":"ejapan_1","Medium":"https:\/\/item-shopping.c.yimg.jp\/s\/h\/ejapan_1"},"IsPMallStore":"false"},"IsAdult":"0","Deliveryinfo":{"Area":"","Deadline":"","Day":""},"_attributes":{"index":"1"}},"1":{"Name":"\u3010\u30ad\u30e3\u30c3\u30b7\u30e5\u30ec\u30b95\uff05\u9084\u5143\u3011\u2605\u307e\u3068\u3081\u8cb7\u3044\u2605\u3000\u30ab\u30eb\u30d3\u30fc\u3000\u304b\u3063\u3071\u3048\u3073\u305b\u3093\u7d00\u5dde\u306e\u6885\u547370g\u3000\u3000\u00d712\u500b\u3010\u30a4\u30fc\u30b8\u30e3\u30d1\u30f3\u30e2\u30fc\u30eb\u3011","Description":"\u3010\u691c\u7d22\u30ad\u30fc\u30ef\u30fc\u30c9\uff08\u5546\u54c1\u5185\u5bb9\u3092\u4fdd\u969c\u3059\u308b\u3082\u306e\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff09\u3011\u304a\u83d3\u5b50\u3000\u304a\u3084\u3064\u3000\u30b9\u30ca\u30c3\u30af\u3000\u30b9\u30ca\u30c3\u30af\u83d3\u5b50\u3000\u30ab\u30c3\u30d1\u30a8\u30d3\u30bb\u30f3\u3000\u3046\u3081\u5473\u3000\u30a6\u30e1\u3000\u6d77\u8001\u3000\u30ab\u30eb\u30b7\u30a6\u30e0","Headline":"","Url":"https:\/\/store.shopping.yahoo.co.jp\/ejapan\/012-4901330197711f.html","ReleaseDate":"","Availability":"instock","Code":"ejapan_012-4901330197711f","Condition":"new","Image":{"Id":"ejapan_012-4901330197711f","Small":"https:\/\/item-shopping.c.yimg.jp\/i\/c\/ejapan_012-4901330197711f","Medium":"https:\/\/item-shopping.c.yimg.jp\/i\/g\/ejapan_012-4901330197711f"},"Review":{"Rate":"0.00","Count":"0","Url":"https:\/\/shopping.yahoo.co.jp\/review\/item\/list?store_id=ejapan&page_key=012-4901330197711f"},"Affiliate":{"Rate":"1.0"},"Price":{"_attributes":{"currency":"JPY"},"_value":"1398"},"PremiumPrice":"","PriceLabel":{"_attributes":{"taxIncluded":"true"},"FixedPrice":"","DefaultPrice":"1398","SalePrice":"","PremiumPriceStatus":"0","PremiumPrice":"1398","PremiumDiscountType":"","PremiumDiscountRate":"","PeriodStart":"","PeriodEnd":""},"Point":{"Amount":"13","Times":"1","PremiumAmount":"13","PremiumTimes":"1"},"Shipping":{"Code":"1","Name":"\u8a2d\u5b9a\u7121\u3057"},"Category":{"Current":{"Id":"13451","Name":"\u305d\u306e\u4ed6\u30b9\u30ca\u30c3\u30af\u3001\u304a\u83d3\u5b50\u3001\u304a\u3064\u307e\u307f"}},"CategoryIdPath":{"0":{"Id":"1"},"1":{"Id":"2498"},"2":{"Id":"4745"},"3":{"Id":"13451"},"_container":"Category"},"Brands":{"Name":"\u30ab\u30eb\u30d3\u30fc","Path":{"0":{"Id":"1"},"1":{"Id":"16267"},"_container":"Brand"}},"JanCode":"4901330197711","Model":"","IsbnCode":"","Store":{"Id":"ejapan","Name":"e\u30b8\u30e3\u30d1\u30f3","Url":"https:\/\/store.shopping.yahoo.co.jp\/ejapan\/","Payment":{"0":{"Code":"1","Name":"\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9"},"1":{"Code":"16","Name":"Yahoo!\u30a6\u30a9\u30ec\u30c3\u30c8\u306b\u767b\u9332\u3057\u3066\u3044\u308b\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9"},"2":{"Code":"4","Name":"\u5546\u54c1\u4ee3\u5f15"},"3":{"Code":"2","Name":"\u9280\u884c\u632f\u8fbc"},"4":{"Code":"8","Name":"\u90f5\u4fbf\u632f\u66ff"},"5":{"Code":"1024","Name":"\u30bd\u30d5\u30c8\u30d0\u30f3\u30af\u307e\u3068\u3081\u3066\u652f\u6255\u3044"},"6":{"Code":"4096","Name":"PayPay"},"_container":"Method"},"IsBestStore":"false","Ratings":{"Rate":"4.5","Count":"11875","Total":"51100","DetailRate":"4.3"},"Image":{"Id":"ejapan_1","Medium":"https:\/\/item-shopping.c.yimg.jp\/s\/h\/ejapan_1"},"IsPMallStore":"false"},"IsAdult":"0","Deliveryinfo":{"Area":"","Deadline":"","Day":""},"_attributes":{"index":"2"}},"2":{"Name":"\u3010\u30ad\u30e3\u30c3\u30b7\u30e5\u30ec\u30b95\uff05\u9084\u5143\u3011\u3010\u9001\u6599\u7121\u6599\u3011\u2605\u307e\u3068\u3081\u8cb7\u3044\u2605\u3000\u30ab\u30eb\u30d3\u30fc\u3000\u304b\u3063\u3071\u3048\u3073\u305b\u3093\u7d00\u5dde\u306e\u6885\u547370g\u3000\u3000\u00d712\u500b\u3010\u30a4\u30fc\u30b8\u30e3\u30d1\u30f3\u30e2\u30fc\u30eb\u3011","Description":"\u3010\u691c\u7d22\u30ad\u30fc\u30ef\u30fc\u30c9\uff08\u5546\u54c1\u5185\u5bb9\u3092\u4fdd\u969c\u3059\u308b\u3082\u306e\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff09\u3011\u304a\u83d3\u5b50\u3000\u304a\u3084\u3064\u3000\u30b9\u30ca\u30c3\u30af\u3000\u30b9\u30ca\u30c3\u30af\u83d3\u5b50\u3000\u30ab\u30c3\u30d1\u30a8\u30d3\u30bb\u30f3\u3000\u3046\u3081\u5473\u3000\u30a6\u30e1\u3000\u6d77\u8001\u3000\u30ab\u30eb\u30b7\u30a6\u30e0","Headline":"","Url":"https:\/\/store.shopping.yahoo.co.jp\/ejapan\/012-4901330197711fm.html","ReleaseDate":"","Availability":"instock","Code":"ejapan_012-4901330197711fm","Condition":"new","Image":{"Id":"ejapan_012-4901330197711fm","Small":"https:\/\/item-shopping.c.yimg.jp\/i\/c\/ejapan_012-4901330197711fm","Medium":"https:\/\/item-shopping.c.yimg.jp\/i\/g\/ejapan_012-4901330197711fm"},"Review":{"Rate":"0.00","Count":"0","Url":"https:\/\/shopping.yahoo.co.jp\/review\/item\/list?store_id=ejapan&page_key=012-4901330197711fm"},"Affiliate":{"Rate":"1.0"},"Price":{"_attributes":{"currency":"JPY"},"_value":"2278"},"PremiumPrice":"","PriceLabel":{"_attributes":{"taxIncluded":"true"},"FixedPrice":"","DefaultPrice":"2278","SalePrice":"","PremiumPriceStatus":"0","PremiumPrice":"2278","PremiumDiscountType":"","PremiumDiscountRate":"","PeriodStart":"","PeriodEnd":""},"Point":{"Amount":"22","Times":"1","PremiumAmount":"22","PremiumTimes":"1"},"Shipping":{"Code":"1","Name":"\u8a2d\u5b9a\u7121\u3057"},"Category":{"Current":{"Id":"13451","Name":"\u305d\u306e\u4ed6\u30b9\u30ca\u30c3\u30af\u3001\u304a\u83d3\u5b50\u3001\u304a\u3064\u307e\u307f"}},"CategoryIdPath":{"0":{"Id":"1"},"1":{"Id":"2498"},"2":{"Id":"4745"},"3":{"Id":"13451"},"_container":"Category"},"Brands":{"Name":"\u30ab\u30eb\u30d3\u30fc","Path":{"0":{"Id":"1"},"1":{"Id":"16267"},"_container":"Brand"}},"JanCode":"4901330197711","Model":"","IsbnCode":"","Store":{"Id":"ejapan","Name":"e\u30b8\u30e3\u30d1\u30f3","Url":"https:\/\/store.shopping.yahoo.co.jp\/ejapan\/","Payment":{"0":{"Code":"1","Name":"\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9"},"1":{"Code":"16","Name":"Yahoo!\u30a6\u30a9\u30ec\u30c3\u30c8\u306b\u767b\u9332\u3057\u3066\u3044\u308b\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9"},"2":{"Code":"4","Name":"\u5546\u54c1\u4ee3\u5f15"},"3":{"Code":"2","Name":"\u9280\u884c\u632f\u8fbc"},"4":{"Code":"8","Name":"\u90f5\u4fbf\u632f\u66ff"},"5":{"Code":"1024","Name":"\u30bd\u30d5\u30c8\u30d0\u30f3\u30af\u307e\u3068\u3081\u3066\u652f\u6255\u3044"},"6":{"Code":"4096","Name":"PayPay"},"_container":"Method"},"IsBestStore":"false","Ratings":{"Rate":"4.5","Count":"11874","Total":"51099","DetailRate":"4.3"},"Image":{"Id":"ejapan_1","Medium":"https:\/\/item-shopping.c.yimg.jp\/s\/h\/ejapan_1"},"IsPMallStore":"false"},"IsAdult":"0","Deliveryinfo":{"Area":"","Deadline":"","Day":""},"_attributes":{"index":"3"}},"_container":"Hit"}}}}
So I would like to find the way to automatically parse this JSON body in order to exploit the data later,
Does the solution come from the encoding format ?
I have not been successful implementing decompressing using Gzip (link here)
Would it be possible to do it automatically via Decodable or the data is maybe directly accessible in Alomofire ?
Thank you in advance,
Charles

When it comes to decoding and holding data from JSON responses from REST APIs, I have used the following approach:
Include the bellow two PODS
1) Alamofire
2) TRON
Now, you'll have to create a class you'll use as a datasource interface between the API and the actual class model. In the following example, I'm pulling data from a movie database service and I have the bellow class for the data sourcing:
class MovieDataSource: JSONDecodable {
var movies: [Movie] = []
let cellid = "cellid"
required init(json: JSON) throws {
guard let moviesArray = json["results"].array else {
print("Error on the JSON Format")
return
}
self.movies = moviesArray.map({return Movie(json: $0)})
}
}
class JSONError: JSONDecodable {
required init(json: JSON) throws {
print("JSON error\n", json)
}
}
Then I have the actual data holding class/structure:
struct Movie {
let title: String
let movieId: Int
let overview: String
let releaseDate: String
let posterPathURL: String
let genres: [Int]
let vote_average: Float
let popularity: Float
let isForAdults: Bool
init(json: JSON) {
self.title = json["title"].stringValue
self.movieId = json["id"].intValue
self.overview = json["overview"].stringValue
self.releaseDate = json["release_date"].stringValue
self.posterPathURL = json["poster_path"].stringValue
self.genres = json["genre_ids"].arrayObject as! [Int]
self.vote_average = json["vote_average"].floatValue
self.popularity = json["popularity"].floatValue
self.isForAdults = json["adult"].boolValue
}
}
I think the above information should be enough to get you an idea on how to move forward, but If you need further guidance, I found this video series very educative on this subject: https://www.youtube.com/watch?v=tt67VikEovY&list=PL0dzCUj1L5JE1wErjzEyVqlvx92VN3DL5&index=13
Good luck!

Related

Using decoded JSON in SwiftUI

I have just started with Swift so please excuse if this is just a stupid question. I am working on my first App which should load a JSON from the web, parses and displays its content to a LazyVGrid. I have started with the apple tutorial which uses a local JSON and everything worked fine.
Now I have changed to the original nested JSON and URL and I can see the output on the console, but I simply do not know how to use it in my LazyVGrid now. I did a lot of research but all good tutorials end with the successful parsing. I ended up with:
func fetchData()
{
let url = URL(string: "https://myjsonurl")!
URLSession.shared.dataTask(with: url) {(json, response, error) in
guard let json = json else {
return
}
let welcome = try! JSONDecoder().decode(Welcome.self, from: json)
print(welcome)
print(welcome.data.data.results[0].title)
}.resume()
}
So as mentioned print() gives me the whole JSON or even specific values like the title. But how can I loop this to my LazyVGrid now?(Xcode is complaining that can not reach it or it is not identifiable because ID is only all the way down in the nested array structure...)
Do I have to create a new array first because the welcome thing is not available outside the fetchData() function? How should it look like to keep the whole JSON structure? I guess all of my experiments have been far too complicated.
I would highly appreciate if someone could give me a hint or an example.
Just in case you need the structure:
// MARK: - Welcome
struct Welcome: Codable {
var data: WelcomeData
}
// MARK: - WelcomeData
struct WelcomeData: Codable {
var success: Bool
var data: DataData
}
// MARK: - DataData
struct DataData: Codable {
var results: [Result]
}
// MARK: - Result
struct Result: Codable {
var id, title, alias, introtext: String
var fulltext, publishUp: String
enum CodingKeys: String, CodingKey {
case id, title, alias, introtext, fulltext
case publishUp = "publish_up"
}
}

Creating Decode Path from JSON Data in Swift that Includes Numbers and Hyphens?

this is relatively new to me and I've searched high and low but have been unsuccessful in finding a similar scenario.
I have retrieved some JSON Data from an API URL and have successfully decoded and output various values from this data as strings by parsing the data to a separate sheet and using structs and constants with the 'Decodable' value set. The problem I have is that one of the containers in the Json data is a hyphenated date in this format dates['2020-11-04'] so swift will not let me create a struct with this name (also this looks like an array but there are no square brackets when viewing the unformatted JSON data in a web browser).
Here is the full path to the date I want to output as a string and the URL being used (copied from a web browser using JSON Viewer Pro):
dates['2020-11-04'].countries.Afghanistan.date
https://api.covid19tracking.narrativa.com/api/2020-11-04
Here is the sheet containing my Structs and constants to decode the data:
import Foundation
//I understand the below name will not work but i've included it to show my presumed process
struct CovidData: Decodable {
let dates: dates[2020-11-04]
}
//Once again the below struct name does not work but i've included it as an example of my presumed process.
struct dates[2020-11-04]: Decodable {
let countries: countries
}
struct countries: Decodable {
let Afghanistan: Afghanistan
}
struct Afghanistan: Decodable {
let date: String
}
Here is my management sheet with my API call and JSON Parse:
import Foundation
protocol CovidDataManagerDelegate {
func didUpdateCovidData(_ covidDataManager: CovidDataManager, covid: CovidModel)
}
struct CovidDataManager {
var delegate: CovidDataManagerDelegate?
let covidURL = "https://api.covid19tracking.narrativa.com/api/2020-11-04"
func getData() {
let urlString = covidURL
performRequest(with:urlString)
}
func performRequest(with urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error")
return
}
if let safeData = data {
if let covid = parseJSON(safeData){
self.delegate?.didUpdateCovidData(self, covid: covid)
}
}
}
task.resume()
}
func parseJSON(_ covidData: Data) -> CovidModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CovidData.self, from: covidData)
let date = decodedData.dates['2020-11-04'].countries.Afghanistan.date
let covid = CovidModel(date: date)
print(date)
return covid
} catch {
print("Error with JSON Parse")
return nil
}
}
}
}
I have not included my UI update sheet as mentioned before the call and decode is working perfectly fine when decoding data with a JSON path made up entirely of strings it is only this container with additional symbols and numbers I am stumped with.
Hopefully I've supplied enough information and apologies if some of the terminology isn't accurate, this is still quite new to me.
Thanks!

Combining multiple JSONs with id

I'm trying to fetch data from a public API. However, all the data I need is accessible only by calling multiple URLs.
However, each JSON provided have a station_id and I'm trying to combine the data based on this value.
I am not sure which strategy I should use to "merge" the results (see code below)
I tried calling both URL at the same time.
Also tried to add the data from the second URL after calling the first URL.
first URL (https://api-core.bixi.com/gbfs/es/station_information.json)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"external_id":"0b100854-08f3-11e7-a1cb-3863bb33a4e4",
"name":"de la Commune / Place Jacques-Cartier",
"short_name":"6026",
"lat":45.50761009451047,
"lon":-73.55183601379395,
"capacity":89,}]
// ...
Second URL (https://api-core.bixi.com/gbfs/en/station_status.json)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"num_bikes_available": 39,
"num_docks_available":50,}]
// ...
Excepted Result (This is the structure I am looking for, not the final code)
{"last_updated":1565466677,
"ttl":10,
"data":
{"stations":
[
{"station_id":"25",
"external_id":"0b100854-08f3-11e7-a1cb-3863bb33a4e4",
"name":"de la Commune / Place Jacques-Cartier",
"short_name":"6026",
"lat":45.50761009451047,
"lon":-73.55183601379395,
"capacity":89,
"num_bikes_available": 39,
"num_docks_available":50}]
//...
Structure I tried to pass the data in
struct BixiApiDataModel: Codable {
let last_updated: Int
let ttl: Int
let data: Stations
}
struct Stations: Codable {
let stations: [Station]
}
struct Station: Codable {
let station_id: String
let num_bikes_available: Int
let num_docks_available: Int
let external_id: String
let name: String
let short_name: String
let lat: Float
let lon: Float
let capacity: Int
}
Calling the URL
class Webservice {
func loadBixiApiDataModel(url: URL, completion: #escaping ([Station]?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let response = try? JSONDecoder().decode(BixiApiDataModel.self, from: data)
if let response = response {
DispatchQueue.main.async {
completion(response.data.stations)
}
}
}.resume()
}
}
I'm trying to display the combined information of a station. I assume the data I fetch after calling the first URL isn't stored when I call the second URL.
Should I call both APIs separately, store the data and then combine everything using the station_id value?
Or is it possible to call each APIs and append the data from the second URL based on the station_id?
Thanks in advance for your help.
I would do it like this
Handle each download separately
Keep the resulting data in separate structs
Merge them into a third struct and then use that third struct internally in the app
Handle each download separately
Download the station information first and store it in a dictionary with station_id as key and then download station status and use the same id to match the downloaded elements
Keep the resulting data in separate structs
Since the content of the downloaded data is quite different between the to API calls I would use two different structs for them, StationInformation and StationStatus. Looking at the type of data you might actually want to download status more often than information which seems to be more static so that is another reason to keep them separate.
Merge them into a third struct...
I would create a third struct that contains information from the two other structs, either as just two properties (shown below) or with properties that are extracted from the others
Here is an example of how the third struct could be implemented
struct Station {
let information: StationInformation
var status: StationStatus?
init(information: StationInformation) {
self.information = information
}
var id: String {
return information.stationId
}
mutating func merge(status: StationStatus) {
guard self.id == status.stationId else { return }
self.status = status
}
}
The download function could be modified to be generic to simplify the code. First the structs need to be modified
struct BixiApiDataModel<T: Decodable>: Decodable {
let data: Stations<T>
}
struct Stations<T: Decodable>: Decodable {
let stations: [T]
}
struct StationInformation: Codable {
let stationId: String
let externalId: String
//... rest of properties
}
struct StationStatus: Codable {
let stationId: String
let numBikesAvailable: Int
let numDocksAvailable: Int
}
then the function signature needs to be changed to
func loadBixiApiDataModel<T: Decodable>(url: URL, completion: #escaping ([T]?) -> ()) {
and the decoding needs to be changed (notice the improved error handling, never use try?)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(BixiApiDataModel<T>.self, from: data)
completion(response.data.stations)
} catch {
print(error)
}
And a simplified example of calling the function (but without any merging)
var informationArray: [StationInformation] = []
var statusArray: [StationStatus] = []
if let url = URL(string: "https://api-core.bixi.com/gbfs/es/station_information.json") {
loadBixiApiDataModel(url: url, completion: {(arr: [StationInformation]?) in
if let arr = arr { informationArray = arr }
print(informationArray.count)
})
} else { print("Not a valid url")}
if let url = URL(string: "https://api-core.bixi.com/gbfs/en/station_status.json") {
loadBixiApiDataModel(url: url, completion: {(arr: [StationStatus]?) in
if let arr = arr { statusArray = arr }
print(statusArray.count)
})
} else { print("Not a valid url")}

ObjectMapper returrning NIL in Swift

I'm trying to use Object mapper https://github.com/Hearst-DD/ObjectMapper to convert a JSON string into a Swift object. Note I've simplified the object to a single field here - obviously my real response has more fields!
My response is:
data arrray response [{
chat = {
"_id" = 1;
}}]
So I want to convert to my chat class:
public class Chat: Mappable {
var _id: String? }
public required init?(map: Map){
}
public func mapping(map: Map) {
_id <- map["_id"]
}
}
So I convert my data array to a dictionary
let jsonResponse = dataArray [0]
let discResponse = jsonResponse as! Dictionary<String,AnyObject>
I can even access my field manually
let chat = discResponse["chat"]
let id = chat!["_id"]
print ("CHAT ID", id)
But mapping to the object
let jsonData = try! JSONSerialization.data(withJSONObject: chat, options: .prettyPrinted)
let user = Chat(JSONString: String( describing: jsonData))
returns nil
Why?
Just putting my comment as an answer, if someone will stuck on the same issue: use Mapper<Chat>().map(JSONObject: chat). It should help your cause.

JSON with Swift 2, Array Structure

I'm Having trouble with JSON and Swift 2.
I'm getting this Array from the server
[{"KidName":"Jacob","KidId":1,"GardenID":0},
{"KidName":"Sarah","KidId":2,"GardenID":0},
{"KidName":"Odel","KidId":3,"GardenID":0}]
I'm familiar with JSON and I know it's not the recommended way to get a JSON, since it's supposed to be something like
{"someArray":[{"KidName":"Jacob","KidId":1,"gardenID":0}, .....
So my first question is it possible to run over the first JSON I've post and get the KidName number without editing the JSON and Add to it a JSON OBJECT to hold the array ?
my second question is really with Swift 2, how can I get the KidName (after I've edited the JSON to have an holder for the array)?
this is my code... (please read the Notes I've added)
BTW, I'm familiar with SwiftyJSON as well...
// Method I've build to get the JSON from Server, the Data is the JSON
sendGetRequest { (response, data ) -> Void in
// need to convert data to String So I can add it an holder
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
/**
after editing the str, i'm Having a valid JSON, let's call it fixedJSON
*/
let fixedJSON = "{\"kidsArray\":\(dropLast)}"
// Now I'm converting it to data back again
let jsonTodata = fixedJSON.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// After Having the data, I need to convert it to JSON Format
do{
let dataToJson = try NSJSONSerialization.JSONObjectWithData(jsonTodata, options: []) as! [String:AnyObject]
//Here I'm getting the KidID
if let kidID = jsonSe["kidsArray"]![0]["KidId"]!!.integerValue {
print("kidID in first index is: \(kidID)\n")
}
//NOW trying to get the KidName which not working
if let kidname = jsonSe["kidsArray"]![0]["KidName"]!!.stringValue {
print("KidName is \(kidname)\n")
}
}
So as you can see, I'm not able to get the KidName.
Any Help Would be Appreciate.
You can use the following function to get the 'someArray' array and then use this getStringFromJSON function to get the 'KidName' value.
func getArrayFromJSON(data: NSDictionary, key: String) -> NSArray {
if let info = data[key] as? NSArray {
return info
}
else {
return []
}
}
let someArray = self.getArrayFromJSON(YourJSONArray as! NSDictionary, key: "someArray")
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String {
return info
}
return ""
}
let KidName = self.getStringFromJSON(someArray as! NSDictionary, key: "KidName")
Hope this might be useful to you.