I'm trying to learn to make an API call in swiftUI, I'm following the next tutorial https://www.youtube.com/watch?v=1en4JyW3XSI but the code is giving me an error that I can't find a solution for.
PostList.swift
import SwiftUI
struct PostList: View {
#State var posts: [Post] = []
var body: some View {
List(posts) { post in
Text(post.title)
}
.onAppear(){
Api().getPosts { (posts) in
self.posts = posts
}
}
}
}
struct PostList_Previews: PreviewProvider {
static var previews: some View {
PostList()
}
}
Data.swift
import SwiftUI
struct Post: Codable, Identifiable {
var id = UUID()
var title: String
var body: String
}
class Api{
func getPosts(completition: #escaping([Post]) -> ()){
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
completition(posts)
}
}
.resume()
}
}
The error that I'm getting is on here let posts = try! JSONDecoder().decode([Post].self, from: data!) and it's the next:
Thread 4: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.typeMismatch(Swift.String,
Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index
0", intValue: 0), CodingKeys(stringValue: "id", intValue: nil)],
debugDescription: "Expected to decode String but found a number
instead.", underlyingError: nil))
I have noticed that the guy in the tutorial uses let id = UUID() but that gives me a problem as well and I'm being asked to change it to var id = UUID().
I'm sorry if it's a very simple or stupid question, I just can't see to find a way around it.
You can see the exact problem by adding try - catch block for the exact error.
Like this
URLSession.shared.dataTask(with: url) { (data, _, _) in
do {
let posts = try JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
completition(posts)
}
} catch {
print(error.localizedDescription)
}
}
So now the error is printed:
The data couldn’t be read because it isn’t in the correct format.
It means you are decoding the wrong type.
The problem is here
struct Post: Codable, Identifiable {
var id = UUID() //< Here
Here in json id have Int type and you are using UUID type.
So just change the data type UUID to Int. Like this
struct Post: Codable, Identifiable {
var id : Int //< Here
Related
I'm new to Swift 5.3 and having trouble retrieving my nested JSON data.
My JSON data result looks like this:
{
"sites":[
{
"site_no":"16103000",
"station_nm":"Hanalei River nr Hanalei, Kauai, HI",
"dec_lat_va":22.1796,
"dec_long_va":-159.466,
"huc_cd":"20070000",
"tz_cd":"HST",
"flow":92.8,
"flow_unit":"cfs",
"flow_dt":"2020-08-18 07:10:00",
"stage":1.47,
"stage_unit":"ft",
"stage_dt":"2020-08-18 07:10:00",
"class":0,
"percentile":31.9,
"percent_median":"86.73",
"percent_mean":"50.77",
"url":"https:\/\/waterdata.usgs.gov\/hi\/nwis\/uv?site_no=16103000"
}
]
}
My structs look like this:
struct APIResponse: Codable {
let sites: APIResponseSites
}
struct APIResponseSites: Codable {
let station_nm: String
let stage: Float
}
And my Decode SWIFT looks like this:
let task = URLSession.shared.dataTask(with: url, completionHandler: {
data, _, error in
guard let data = data, error == nil else {
return
}
var result: APIResponse?
do {
result = try JSONDecoder().decode(APIResponse.self, from: data)
}
catch {
print("Failed to decode with error: \(error)")
}
guard let final = result else {
return
}
print(final.sites.station_nm)
print(final.sites.stage)
})
And of course, I get an error that states:
Failed to decode with error:
typeMismatch(Swift.Dictionary<Swift.String, Any>,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"sites", intValue: nil)], debugDescription: "Expected to decode
Dictionary<String, Any> but found an array instead.", underlyingError:
nil))
I know it has to do with 'sites' returning an array (a single one) but I don't know how to fix it. Any help would be greatly appreciated.
The error message it is pretty clear you need to parse an array of objects instead of a single object.
Just change your root declaration property from
let sites: APIResponseSites
to
let sites: [APIResponseSites]
**1.** First "sites" is an array so replace
let sites: APIResponseSites
with
let sites: [APIResponseSites]()
**2.** As sites is a array collection, please print value like given below:
print(final.sites.first?.station_nm ?? "")
print(final.sites.first?.stage ?? 0.0)
Final code is here:
struct APIResponse: Codable {
let sites: [APIResponseSites]()
}
struct APIResponseSites: Codable {
let station_nm: String
let stage: Float
}
let task = URLSession.shared.dataTask(with: url, completionHandler: {
data, _, error in
guard let data = data, error == nil else {
return
}
var result: APIResponse?
do {
result = try JSONDecoder().decode(APIResponse.self, from: data)
}
catch {
print("Failed to decode with error: \(error)")
}
guard let final = result else {
return
}
print(final.sites.first?.station_nm ?? "")
print(final.sites.first?.stage ?? 0.0)
})
I am trying to fetch information from a JSON file, here are the contents of it
{ "question" : "https://www.sanfoh.com/uob/smile/data/s117b97da1ca0c9cbd2836d8af2n886.png" , "solution" : 6 }
However I am getting this error every time I try to fetch information from it
"typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))"
Here is my code
`
import Foundation
struct Game: Hashable, Codable {
let question: String
let solution: Int
}
class ViewModel: ObservableObject {
#Published var games: [Game] = []
func fetch() {
guard let url = URL(string: "https://marcconrad.com/uob/smile/api.php") else {
return
}
let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data, error == nil else {
return
}
do {
let games = try JSONDecoder().decode([Game].self, from: data)
DispatchQueue.main.async {
self?.games = games
print(games)
}
}
catch {
print(error)
}
}
task.resume()
}
}
`
I have tried removing the square brackets
`
let games = try JSONDecoder().decode(Game.self, from: data)
`
however I then get an error on this line
`
self?.games = games
`
The error is "Cannot assign value of type 'Game' to type '[Game]'
The response is not an array, it is a dictionary.
So you should parse it this way:
let games = try JSONDecoder().decode(Game.self, from: data)
Also, you should update the variable's type like
#Published var games: Game?
While trying to decode JSON, I ran into an error:
Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "cast",
intValue: nil), Swift.DecodingError.Context(codingPath: [],
debugDescription: "No value associated with key
CodingKeys(stringValue: "cast", intValue: nil) ("cast").",
underlyingError: nil))
What is strange, it does not always appear, I can open MovieDetailsView several times without errors, but more often it occurs. What could be the problem?
Data model:
struct MovieCreditResponse: Codable {
let cast: [MovieCast]
}
struct MovieCast: Identifiable, Codable {
let id: Int
let character: String
let name: String
let profilePath: String?
}
Here I fetch data:
class TMDbApi {
//...
func getMovieCredits(movieID: String, completion:#escaping (MovieCreditResponse) -> ()){
guard let url = URL(string: "https://api.themoviedb.org/3/movie/\(movieID)/credits?api_key=<api_key>") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let movies = try! JSONDecoder().decode(MovieCreditResponse.self, from: data!) //ERROR IS HERE
DispatchQueue.main.async {
completion(movies)
}
}
.resume()
}
}
MovieDetailsView:
struct MovieDetailsView: View {
var movie: Movie
#State var casts: [MovieCast] = []
var body: some View {
VStack{
MoviePosterView(posterPath: movie.posterPath!)
List{
ForEach(casts){ cast in
Text(cast.name)
}
}
}.onAppear{
TMDbApi().getMovieCredits(movieID: movie.id.uuidString){ data in
self.casts = data.cast
}
}
}
}
ContentView:
struct ContentView: View {
#State var movies: [Movie] = []
var body: some View {
NavigationView{
List {
ForEach(movies) { movie in
NavigationLink(destination: MovieDetailsView(movie: movie)){
//...
}
}
}.onAppear(){
TMDbApi().getMovies{ data in
self.movies = data.results
}
//...
}
.navigationTitle("Movies App")
}
}
}
Finally I found what caused the error. I used UUID type instead of Int for id property in the movie model (this is the id I used in the URL to query movie casts). Therefore, in my request, the movie ID was of the form "F77A9A5D-1D89-4740-9B0D-CB04E75041C5" and not "278". Interestingly, sometimes this did not cause errors, which confused me (I still do not know why this sometimes worked)
So, I replaced
struct Movie: Identifiable, Codable{
let id = UUID()
//...
}
with
struct Movie: Identifiable, Codable{
let id: Int
//...
}
Thanks to everyone who helped me find a solution
If your response might contain an error (errors do happen!), your app should be aware and handle it.
struct MovieCreditResponse: Codable {
let success : Bool
let status_code : Int
let status_message : String?
let cast: [MovieCast]?
}
then when you get your response, check for success and have your app handle errors:
do {
guard let d = data else {
// handle null data error
}
let responseObject = try JSONDecoder().decode(MovieCreditResponse.self, from: d)
if responseObject.success {
guard let cast = responseObject.cast as? [MovieCast] else {
// handle error of null cast here
}
// this is the happy path: do your thing
} else {
if let errorMessage = responseObject.status_message {
// handle the case of an identified error
handleError(errorMessage)
} else {
// handle the case where something went wrong and you don't know what
}
}
} catch {
// handle decoding error here
}
I am trying to call an API and I am trying to map the response to a model class. But each time, I am getting a an error that invalid data.
Api call is as follows.
let endpoint = "http://apilayer.net/api/live?access_key=baab1114aeac6b4be74138cc3e6abe79¤cies=EUR,GBP,CAD,PLN,INR&source=USD&format=1"
guard let url = URL(string: endpoint) else {
completed(.failure(.invalidEndPoint))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let _ = error {
completed(.failure(.unableToComplete))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(.invalidResponse))
return
}
guard let data = data else {
completed(.failure(.invalidData))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let convertedValues = try decoder.decode([Converted].self, from: data)
completed(.success(convertedValues))
} catch {
completed(.failure(.invalidData))
}
}
task.resume()
}
Sample Response is as follows.
{
"success":true,
"terms":"https:\/\/currencylayer.com\/terms",
"privacy":"https:\/\/currencylayer.com\/privacy",
"timestamp":1610986207,
"source":"USD",
"quotes":{
"USDEUR":0.828185,
"USDGBP":0.736595,
"USDCAD":1.276345,
"USDPLN":3.75205
}
}
Model class
struct Converted: Codable {
let success: Bool
let terms, privacy: String?
let timestamp: Int?
let source: String?
let quotes: [String: Double]?
}
Can someone help me to understand where I am getting wrong?. Thanks in advance.
Error messages can sometimes help. If you catch the error and print it, it reads:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
So an array of Converted was expected when in fact a single object was returned. The solution is to update the expectation to match the actual response:
let convertedValues = try decoder.decode(Converted.self, from: data)
I receive the following error:
Error serialising json typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
Code:
//---------------
struct Currency: Decodable {
let symbol: String
let price: String
}
var myDict: [Currency] = []
//---------------
func testParse(){
let jsonUrlString = "https://api.binance.com/api/v3/ticker/price"
guard let url = URL(string: jsonUrlString) else
{ return }
URLSession.shared.dataTask(with: url) { (data,response,err) in
guard let data = data else
{
print("Error: No data to decode")
return
}
do
{
let exchanges = try
JSONDecoder().decode(Currency.self, from: data)
let X: [Currency] = [exchanges]
self.myDict = X
self.testFunc()
print("binance: "+self.myDict[0].symbol + ": "+self.myDict[0].price)
}
catch let jsonErr
{
print("Error serialising json",jsonErr)
}
}
.resume()
}
Is the issue with my struct layout? Or would it be how I'm parsing? I'd like to get a better understanding here for future reference. Therefore, if anyone could link a good Swift 4 guide it would be greatly appreciated. Alternatively, if you could give me a detailed answer that would be great (rather than spoon feeding the answer where I don't learn).
Please read the error message carefully and learn to understand it. It's very clear.
Expected to decode Dictionary but found an array instead
In other words: You want to decode a dictionary (Currency) but in truth it's an array ([Currency]).
In terms of Decodable a dictionary is the target struct or class.
And please don't name an object as ...dict which is actually an array.
var myArray = [Currency]()
...
let exchanges = try JSONDecoder().decode([Currency].self, from: data)
self.myArray = exchanges