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?
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'm trying to do with API Call. I got error every time trying to do API Call.
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
this is what i see when simulate my code in console.
Here is json format i try to call in my app. Click
My Model
struct Article: Codable {
let author: String
let title, articleDescription: String
let url: String
let urlToImage: String
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
and This is my API Call function.
import UIKit
class ViewController: UIViewController {
var article = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
jsonParse {
print("success")
}
view.backgroundColor = .red
}
func jsonParse(completed: #escaping () -> ()) {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=tr&apiKey=1ea9c2d2fbe74278883a8dc0c9eb912f")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if error != nil {
print(error?.localizedDescription as Any)
}else {
do {
let result = try JSONDecoder().decode([Article].self, from: data!)
DispatchQueue.main.async {
print(data as Any)
print("success")
self.jsonParse {
print("success")
}
}
}catch {
print(error.localizedDescription)
}
}
}
task.resume()
}
}
Can you help me about my problem, thank you.
This is a very common mistake: You ignore the root object, a dictionary. With Decodable it's mandatory to decode the JSON from the top.
Add this struct
struct Response: Decodable {
let status: String
let totalResults: Int
let articles: [Article]
}
and decode
let result = try JSONDecoder().decode(Response.self, from: data!)
And never print(error.localizedDescription) in a Codable catch block. Always write this
} catch {
print(error)
}
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 would like to assign a value from JSON to a variable, the issue is Swift thinks I am passing an entire array and not just the JSON value of code to the variable.
I have the following structure and JSON decode function:
private func JSONFunction() {
guard let url = URL(string: "https://example.com/example/example"),
let nameValue = stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "name=\(nameValue)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let myData = try JSONDecoder().decode(codeStruct.self, from:data)
DispatchQueue.main.async {
codeNum = myData.code
print(codeNum)
}
}
catch {
print(error)
}
}.resume()
}
The following is the structure for decoding the JSON:
struct codeStruct: Codable {
let code: String
let line: String
let person: String
let maker: String
}
Error received:
typeMismatch(Swift.Dictionary,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Dictionary but found an array
instead.", underlyingError: nil))
Without looking at the json, if I were to guess, I would say that your incoming JSON is actually an array of codeStruct objects, for which you should change your line to
let myData = try JSONDecoder().decode([codeStruct].self, from:data)