0

I'm getting this error: "Expected to decode Array<Any> but found a dictionary instead.", while trying to decode Dictionary JSON?

JSON looks like this:

{
  "status": "success",
  "totalResults": 69,
  "results": []
}

How can i skip status and totalResults and decode only results?

My code:

struct News: Decodable {
  var title: String
  var content: String
}

let jsonUrl = URL(string: "https://newsdata.io/api/1/news?apikey=pub_297524faa4f21de311b826df181186f8e33b&q=travelling&language=en")

var news = [News]()

URLSession.shared.dataTask(with: jsonUrl!) { data, response, error in
    do {
        news = try JSONDecoder().decode([News].self, from: data!)
        news.forEach { print($0.title) }
    } catch {
        print(error)
    }
}.resume()

PS asking here because 99% youtube tutorials explain only how to decode an array

ImAlex2000
  • 15
  • 2
  • `.decode([News].self, from: data!)`: Why did you write `[New].self` and not `New.self`? I know the meaning of both, but I'm asking you why (to make you understand). – Larme Dec 23 '21 at 10:02
  • it's my first try, and all of this done by me watching youtube tutorials :) I'm still not fully understand what's going on – ImAlex2000 Dec 23 '21 at 10:06
  • try JSONDecoder().decode(News.self, from: data!) try this. you are getting the response as JSON Object not as Array. so it shouldn't be [News] – Jok3r Dec 23 '21 at 10:09
  • _"How can i skip status and totalResults and decode only results?"_, you can omit those fields if you want to but you still need to decode from the top so you need a struct that holds the `result` array and use that when decoding. – Joakim Danielson Dec 23 '21 at 13:38

1 Answers1

-1

to decode your json data you must first get the correct swift data model that match the data. A quick way to do this, is to copy and paste your json data into "https://quicktype.io/". This site will give you the swift structs you need. Then you need to make a request call to the server. Here is the code that does this, given the swift data structs.

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State var results = [Result]()
    
    var body: some View {
        List {
            ForEach(results) { news in
                VStack {
                    Text(news.title)
                    Text(news.link).foregroundColor(.blue)
                }
            }
        }
        .onAppear {
            loadNews()
        }
    }
    
    func loadNews() {
        let jsonUrl = URL(string: "https://newsdata.io/api/1/news?apikey=YOURAPIKEY&q=travelling&language=en")
        guard let url = jsonUrl else { return }
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            do {
                let response = try JSONDecoder().decode(Response.self, from: data)
                results = response.results
            }
            catch {
                print("---> error: \(error)")
            }
        }
        task.resume()
    }
}

struct Response: Codable {
    let status: String
    let totalResults: Int
    let results: [Result]
    let nextPage: Int
}

struct Result: Identifiable, Codable {
    let id = UUID()
    let title: String
    let link: String
    let keywords: [String]?
    let creator: [String]?
    let videoURL: JSONNull?
    let resultDescription, content: String?
    let pubDate: String
    let fullDescription: String?
    let imageURL: String?
    let sourceID: String

    enum CodingKeys: String, CodingKey {
        case title, link, keywords, creator
        case videoURL = "video_url"
        case resultDescription = "description"
        case content, pubDate
        case fullDescription = "full_description"
        case imageURL = "image_url"
        case sourceID = "source_id"
    }
}

class JSONNull: Codable, Hashable {

    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
        return true
    }

    public var hashValue: Int {
        return 0
    }

    public init() {}

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

PS: do not post your secret API key. Remove it from your question, now.

EDIT-1:

if you really need for some reason, not to decode status and totalResults make the properties let and give them an initial value, as shown below. Xcode will tell you these will not be decoded.

struct Response: Codable {
    let results: [Result]
    let status: String = ""   // <-- here give the let a value
    let totalResults: Int = 0 // <-- here give the let a value
    let nextPage: Int = 0     // <-- here give the let a value
}

or remove those fields if you never want to use them:

struct Response: Codable {
    let results: [Result]
}