I am really new to swift. I am trying an API call, but it just returns an empty array. I am really not sure where I am going wrong. I have been following a couple of tutorials, but it just returns an empty array.
I am not sure if I have defined the model properly or maybe it's going wrong where I am trying to fetch data from the API. I have tried used POSTMAN, to make sure the API is working and it's pulling back results I am trying to pull data from this API here https://docs.trefle.io/docs/advanced/plants-fields#tag/Corrections/operation/createCorrection . I have attached my below code here
import UIKit
import SwiftUI
struct PlantResponse: Codable {
let data : [Plant]
}
struct Plant: Codable {
let id: Int
let common_name: String
let slug: String
let scientific_name: String
let year: Int
let bibliography: String
let author: String
let status: String
let rank: String
let family_common_name: String
let family: String
let genus_id: Int
let genus: String
let image_url: String
var synonyms = [String]()
var links = [String]()
}
func fetchPlantsFromAPI() async throws -> [Plant] {
let url = URL(string: "https://trefle.io/api/v1/plants?token=.[MYTOKEN]&filter[common_name]=beach%20strawberry")!
let (data, _) = try await URLSession.shared.data(from: url)
do {
let decoded = try JSONDecoder().decode(PlantResponse.self, from: data)
return decoded.data
} catch {
print(error)
return [Plant]()
}
}
Task {
try await fetchPlantsFromAPI()
}
Below is the output I get when I run it on playground
Below is the output I get when I run on POSTMAN
{ "data": [ { "id": 263319, "common_name": "Beach strawberry", "slug": "fragaria-chiloensis", "scientific_name": "Fragaria chiloensis", "year": 1768, "bibliography": "Gard. Dict. ed. 8 : n.° 4 (1768)", "author": "(L.) Mill.", "status": "accepted", "rank": "species", "family_common_name": "Rose family", "genus_id": 12147, "image_url": "https://bs.plantnet.org/image/o/8ee87e6f94833055db1c7df5fc07761852b7b1eb", "synonyms": [ "Fragaria vesca var. chiloensis", "Potentilla chiloensis" ], "genus": "Fragaria", "family": "Rosaceae", "links": { "self": "/api/v1/species/fragaria-chiloensis", "plant": "/api/v1/plants/fragaria-chiloensis", "genus": "/api/v1/genus/fragaria" } } ], "links": { "self": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry", "first": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry&page=1", "last": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry&page=1" }, "meta": { "total": 1 } }
Thanks for your help.
The Plant
definition does not match the JSON payload. As a result, JSONDecoder
is undoubtedly throwing an error (which was undoubtedly printed to the console). You must look at the console to see what the error was.
As an aside, I would advise against return [Plant]()
(returning an empty array). Instead, I would print the error and then rethrow it with throw error
. That way, the caller can catch any errors, and reflect that in the UI accordingly. This is probably why it “returns an empty array”, not because the network response was empty, but rather that there was a decoding error and fetchPlantsFromAPI
caught the error, printed it, but then immediately returned an empty array.
FWIW, when I decoded your JSON, I received the following error:
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "links", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
That error is telling you that links
is a dictionary in the JSON, but not defined as such in your object. Instead one might want to define links
in Plant
to be a dictionary, [String: String]
or [String: URL]
. Or, better, given that the keys used in this dictionary are fixed, it would be better to use custom subtype:
struct Plant: Codable {
let id: Int
let commonName: String // camelCase
let slug: String
let scientificName: String // camelCase
let year: Int
let bibliography: String
let author: String
let status: String
let rank: String
let familyCommonName: String // camelCase
let family: String
let genusId: Int // camelCase
let genus: String
let imageUrl: String // camelCase
let synonyms: [String] // an array of strings
let links: Links // a custom type, defined below
}
extension Plant {
struct Links: Codable {
let `self`: String
let plant: String
let genus: String
}
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoded = try decoder.decode(PlantResponse.self, from: data)
print(decoded)
} catch {
print(error)
}
Note, in addition to redefining links
, I also used the standard camelcase property names in Plant
, and instructed the JSONDecoder
to convert the JSON snakecase keys to camelcase property names.
But do not get lost in these details: The key observation is that you should look at the Error
that was thrown to diagnose what went wrong.
Just so we can more clearly read the JSON, here it is in “pretty” format:
{
"data": [
{
"id": 263319,
"common_name": "Beach strawberry",
"slug": "fragaria-chiloensis",
"scientific_name": "Fragaria chiloensis",
"year": 1768,
"bibliography": "Gard. Dict. ed. 8 : n.° 4 (1768)",
"author": "(L.) Mill.",
"status": "accepted",
"rank": "species",
"family_common_name": "Rose family",
"genus_id": 12147,
"image_url": "https://bs.plantnet.org/image/o/8ee87e6f94833055db1c7df5fc07761852b7b1eb",
"synonyms": [
"Fragaria vesca var. chiloensis",
"Potentilla chiloensis"
],
"genus": "Fragaria",
"family": "Rosaceae",
"links": {
"self": "/api/v1/species/fragaria-chiloensis",
"plant": "/api/v1/plants/fragaria-chiloensis",
"genus": "/api/v1/genus/fragaria"
}
}
],
"links": {
"self": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry",
"first": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry&page=1",
"last": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry&page=1"
},
"meta": {
"total": 1
}
}
FWIW, I use https://jsonlint.com to check the JSON and render it in a pretty format.
Again, the links
in this JSON is a dictionary which can be decoded as its own type.