My app parses a number of APIs in Swift some of which share similar names for dictionaries and/or arrays.
I can't control the names of the incoming JSON dictionaries arrays as they come from API endpoints. Currently I have one utility class that processes all these APIs. How can I parse APIs that share common names?
Here is what I'm using for one API.
struct aVideo: Codable {
let page, totalResults, totalPages: Int
let results: [Result]
enum CodingKeys: String, CodingKey {
case page
case results
}
}
// MARK: - Result
struct Result: Codable {
let popularity: Double
let voteCount: Int
let video: Bool
}
This other API uses the same key, Result and it is throwing an error.
// MARK: - WordInfo
struct WordInfo: Codable {
let word: String
let results: [Result]
let frequency: Double
}
// MARK: - Result
struct Result: Codable {
let definition, partOfSpeech: String
let synonyms, entails, hasTypes, derivation: [String]
}
Thanks for any suggestions.
The JSON itself doesn't define the name of the structure -- only the name of the property that holds it, so you're safe to redefine the names of your structs as needed:
struct WordInfo: Codable {
let word: String
let results: [WordInfoResult]
let frequency: Double
}
// MARK: - Result
struct WordInfoResult: Codable {
let definition, partOfSpeech: String
let synonyms, entails, hasTypes, derivation: [String]
}
The decoder will know that when it gets to results on WordInfo that it should be parsed as the type [WordInfoResult] because that's how you've defined it.
Related
I am learning Swift and would like help on the following:
I am reading in the following JSON file format:
{
"usd":{
"code":"USD",
"rate":0.087722492431105,
"inverseRate":11.399584898769
},
"eur":{
"code":"EUR",
"rate":0.074110125979945,
"inverseRate":13.493432736447
}
}
Struct Currencies: Codable {
let usd: USD
let eur: EUR
struct USD: Codable {
let name: String
let rate: Double
let inverseRate: Double
}
struct EUR: Codable {
let name: String
let rate: Double
let inverseRate: Double
}
I would like to be able to pass in a parameter variable XXXXX that would let me print out for example (Currencies.XXXXX.rate), where I could let XXXXX to be eur, usd, etc. What would be the best way to approach this dataset and/or problem of calling the right variable?
Thanks in advance
I elaborate a little for you.
So when you decode to your first struct, you are decoding to type USD for the string usd in your Json. USD also has properties they are used to decode the dictionary for use in JSON.
let currencies = // Your decode strategy
currencies.usd //is a struct of USD
currencies.usd.rate //calls the property rate on type USD on type Currencies
If you're asking about decode strategy, you may want to rephrase the question. For example:
let json = """
{
"usd":{
"code":"USD",
"rate":0.087722492431105,
"inverseRate":11.399584898769
},
"eur":{
"code":"EUR",
"rate":0.074110125979945,
"inverseRate":13.493432736447
}
}
""".data(using: .utf8)
let cur = try? JSONDecoder().decode(Currencies.self, from: json!)
print( cur!.usd.rate )
You can try
let res = try JSONDecoder().decode([String:Currency].self,from:data)
print(Array(res.keys))
print(res["usd"])
With
struct Currency: Codable {
let name: String
let rate,inverseRate: Double
}
I try do decode and access specific exchange rate for specific currency using Alamofire and Swift decode:
this is my model:
struct Listek: Codable {
let base: String
let date: String
let rates: [String: Double]
enum CodingKeys: String, CodingKey {
case base = "base"
case date = "date"
case rates = "rates"
}
}
this is Alamofire API call + decode
let apiToContact = "https://api.exchangeratesapi.io/latest"
AF.request(apiToContact).responseJSON { (response) in
print(response)
guard let data = response.data else { return }
do {
let st = try JSONDecoder().decode(Listek.self, from: data)
print (st.rates)
print (st.base)
print (st.date)
}
catch {
print("error")
}
So far so good, but I fail in accessing the single currency and its rate value. I would like declare a variable "JPYrate" with value of JPY rate from JSON. Can you please navigate me?
You can simply get the value corresponding to key JPY from rates Dictionary like so,
let JPYrate = st.rates["JPY"]
Also, there is no need to create enum CodingKeys, if the key names are same as the property names. So, your struct Listek looks like,
struct Listek: Codable {
let base: String
let date: String
let rates: [String:Double]
}
exchangeratesapi seems to send consistent data. if so my suggestion is to decode the rates into a dedicated struct
struct Listek: Decodable {
let base: String
let date: String
let rates: Rates
}
struct Rates: Decodable {
let CAD, HKD, ISK, PHP, DKK, HUF, CZK, AUD, RON, SEK, IDR, INR, BRL, RUB, HRK, JPY, THB, CHF, SGD, PLN, BGN, TRY, CNY, NOK, NZD, ZAR, USD, MXN, ILS, GBP, KRW, MYR : Double
}
Then you can get the rate directly
print(st.rates.JPY)
First, you can make a computed property to access the JPY rate:
var jpyRate: Double? { rates["JPY"] }
Or parse the rates into a specific type as recommended by #vadian.
Second, you can use Alamofire's responseDecodable method to do the decoding automatically.
AF.request(apiToContact).responseDecodable(of: Listek.self) { response in
debugPrint(response)
}
I'm decoding a simple structure and ran into unexpected behavior.
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient]
}
do {
let jsonData = SOME STING WITH VALID JSON
let contacts = try JSONDecoder().decode(Contacts.self, from: jsonData)
}
catch {
}
This decodes just fine. If I do this simple change to the structure, it no longer decodes.
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient] = []
}
Now JSONDecoder won't decode the exact same string. Why does the default initialization of the array cause the decoder to stop working?
Change your code to read:
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
var recipients: [Recipient] = []
}
The issue (as described in the comments above) is that your initial declaration of the variable is immutable (meaning it cannot be changed after it is initialized). So, if you declare the initial value for a let as [] then you cannot subsequently change it. By changing let to var you are declaring the variable as mutable (meaning it can be changed) which allows you to both supply an initial value as well as change the value later.
Your let here isn't a "default initialization." It's defining recipients as a constant with a specific, compile-time, value. If you want a default value, then you can create a initializer for that if you like:
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient]
init(recipients: [Recipient] = []) { self.recipients = recipients }
}
Generally speaking on this kind of very simple data type, I'd make recipients a var instead (see John Ayers's answer for example). It's much more flexible, while maintaining all the advantages of a value type. But in cases where you want a let constant with a default (rather than a single value), you need an init.
I have this kind of data. But I haven’t serialised it yet into JSON
{
"status":"ok",
"totalResults":5899,
"articles":[{//some key value pairs},
{//some key value pairs}
]
}
I want to parse array of articles using decodable protocol.
I know how to do this if I have only articles array but in above case how can I first find the data of articles and parse it to my model using JSONDecodable.
Firstly declare struct of those types.
struct Root : Decodable {
let status : String
let totalResults : Int
let articles : [Article]
}
struct Article : Decodable {
{//some key value pairs},
{//some key value pairs}
}
Suppose the json string is jsonStr.
Now convert this json into data.
let data = Data(jsonStr.utf8)
Now try to decode this data.
let decodedStruct = fromJSON(data)
Here is the definition of fromJSON() method
static func fromJSON(jsonData: Data) -> Root? {
let jsonDecoder = JSONDecoder()
do {
let root = try jsonDecoder.decode(Root.self, from: jsonData)
return root
} catch {
return nil
}
}
A dictionary becomes a struct and an array of dictionaries becomes an array of the struct
struct Root : Decodable {
let status : String
let totalResults : Int
let articles : [Article]
}
struct Article : Decodable {
let aKey : AType
let anotherKey : AnotherType
}
I know this has been covered in other questions, but I've followed them and I'm still stumped. Here is my JSON structure:
{
"FindBoatResult": {
"num_boats": 10,
"boat": [
{
"num_segments": 1,
"segments": [
{
"ident": "String",
"origin" : {
"code" : "String"
},
},
]
}
etc...but thats as deep as the structure goes. there are multiple returns of "segments" in each JSON response. In Swift I have this code.
struct Result : Decodable {
let FindBoatResult : FindBoatResult
}
struct FindBoatResult : Decodable {
let boats : Boats
let num_boats : Int
}
struct Boats : Decodable {
let segments : [Segments]
}
struct Segments : Decodable {
let ident : String?
let origin : Origin
}
struct Origin : Decodable {
let code : String
}
func getBoats() {
let urlString = "http://myApi"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
let dataAsString = String(data: data, encoding: .utf8)
//print(dataAsString)
do {
let boats = try
JSONDecoder().decode(FindBoatResult.self, from: data)
print(boats)
} catch {
print(err)
}
}.resume()
}
This fails and throws err but err prints as nil..so I can't tell what I'm missing. dataAsString prints out the JSON as expected, so I know "data" is good.
I detected a couple of minor issues. Try replacing this:
struct FindBoatResult: Decodable {
let boats: Boats
let num_boats: Int
}
struct Boats: Decodable {
let segments: [Segments]
}
with:
struct FindBoatResult: Decodable {
let boat: [Boat]
let num_boats: Int
}
struct Boat: Decodable {
let segments: [Segments]
}
Finally, decode using the Result type (not FindBoatResult):
JSONDecoder().decode(Result.self, from: data)
Expanding on Paulo's answer, I might further suggest that if you're stuck with JSON that has keys that don't conform to Swift conventions for property names, that you use the CodingKeys pattern to translate JSON keys to better Swift property names, e.g.:
struct BoatResult: Decodable { // I'd simplify this name
let boatCollection: BoatCollection
enum CodingKeys: String, CodingKey {
case boatCollection = "FindBoatResult"
}
}
struct BoatCollection: Decodable { // I'd simplify this, too, removing "Find" from the name; verbs are for methods, not properties
let boats: [Boat]
let numberOfBoats: Int
enum CodingKeys: String, CodingKey {
case boats = "boat" // "boat" isn't great property name for an array of boats, so let's map the poor JSON key to better Swift name here
case numberOfBoats = "num_boats" // likewise, let's map the "_" name with better camelCase property name
}
}
struct Boat: Decodable { // This entity represents a single boat, so let's use "Boat", not "Boats"
let segments: [Segment]
}
struct Segment: Decodable { // This entity represents a single segment, so let's use "Segment", not "Segments"
let identifier: String
let origin: Origin
enum CodingKeys: String, CodingKey {
case identifier = "ident" // `ident` isn't a common name for identifier, so let's use something more logical
case origin
}
}
struct Origin: Decodable {
let code: String
}
So, for example, use a plurals (e.g. boats) when you're representing an array of objects, and use CodingKeys to map the misleading boat JSON key to this better named boats array reference. Or when you have a key like num_boats, don't feel like you have to use that bad name in your Swift property and use something better like numberOfBoats (or count or whatever), and lose the _ syntax which is very unSwifty.
Clearly, if you're in control of the design of the JSON, you can just fix some of these poorly chosen key names there, but even where you decide you want your web service to use the _ syntax, go ahead and use CodingKeys to make sure your Swift objects honor the camelCase convention.