0

I'm attempting to get some data from an API and when printing the results, I keep running into this error:

typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "response", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))

These are the structs

struct Status: Decodable {
    let status: String
    let response: [Response]
}

struct Response: Decodable {
    let docs: [Doc]
}

struct Doc: Decodable {
    let webUrl: String
    let abstract: String

    enum CodingKeys: String, CodingKey {
        case webUrl = "web_url"
        case abstract
    }

    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        abstract = try container.decode(String.self, forKey: .abstract)
        webUrl = try container.decode(String.self, forKey: .webUrl)
    }

}

And I call fetchData in my viewDidLoad, then my array var storyData = [Doc]() is populated with the results:

    fetchData(url: jsonUrl) { (result: FetchResult<Status>) in
        switch result {
        case .success(let object): self.storyData = object.response.flatMap{$0.docs}
        print("Results \n\n\n\n \(object.response.flatMap{$0.docs})")
        case .failure(let error): print(error)
        }
    }

I'm not sure what to change here to get this to work. I've tried using quicktype.io to compare my code and I have my structs set up almost exactly the same way as it's generated there. This is what's generated on that site, for reference: https://app.quicktype.io?share=GGomMYH27NtkVpxXiAxB

I've checked out this question and from the solution posted by @vadian I gather that, in my case, the JSON member response is a dictionary and docs is an array of dictionaries - however I'm confused because an array of dictionaries is still an array, right? Also based off of his solution, I'm decoding the initial struct Status, and then through that accessing the response and finally getting what I need in docs:

self.storyData = object.response.flatMap{$0.docs}

How else can I approach this to get rid of the error?

KingTim
  • 1,281
  • 4
  • 21
  • 29
  • Making that change (and changing `self.storyData = object.response.flatMap{$0.docs}` to `self.storyData = object.response.docs` ) results in this error when trying to print the data: `keyNotFound(CodingKeys(stringValue: "abstract", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "response", intValue: nil), CodingKeys(stringValue: "docs", intValue: nil), _JSONKey(stringValue: "Index 2", intValue: 2)], debugDescription: "No value associated with key CodingKeys(stringValue: \"abstract\", intValue: nil) (\"abstract\").", underlyingError: nil))` – KingTim Nov 02 '18 at 16:31
  • Possible duplicate of [Expected to decode Array but found a dictionary instead](https://stackoverflow.com/questions/48007348/expected-to-decode-arrayany-but-found-a-dictionary-instead) – Scriptable Nov 02 '18 at 16:35
  • The error is self explanatory, the response is a dictionary, not an array. the error above is that there was no 'abstract' in the JSON that was expected – Scriptable Nov 02 '18 at 16:37
  • Learn to **read** JSON. It's very easy. There are only two collection types (*array* `[]` and *dictionary* `{}`) and 4 value types. And the `Codable` error messages are one to the most descriptive messages ever. – vadian Nov 02 '18 at 16:52
  • If you look at what quicktype generated and compare it to your objects you'll see that you declared your response as `let response: [Response]` when it should be `let response: Response` – dan Nov 02 '18 at 16:56
  • @vadian I've read the JSON and the error message and am still struggling to implement a solution, hence the question. I've read through guides and other SO answers but still can't figure it out in my particular case. – KingTim Nov 02 '18 at 16:57
  • @dan I've tried making that change, see my first comment – KingTim Nov 02 '18 at 16:58
  • OK, learn to **understand** there error messages. The `{` clearly indicates that `response` is a dictionary so remove the `[]` (`let response: Response`) and if there is ***No value associated*** make the struct member optional (`let abstract: String?`) – vadian Nov 02 '18 at 17:02
  • I've made that change and even making `abstract` and `webUrl` optional still results in the same error message as my first comment. Which is confusing because the error says `"No value associated with key CodingKeys(stringValue: \"abstract\", intValue: nil) (\"abstract\").", underlyingError: nil))`, but looking at the JSON, there IS a value for abstract. – KingTim Nov 02 '18 at 17:05
  • Delete the `init` method or use `decodeIfPresent` for `abstract`. – vadian Nov 02 '18 at 17:09
  • Alright that seems to have worked - can I ask why the `init` method was getting in the way? – KingTim Nov 02 '18 at 17:13
  • In most cases you don't need the `init` method because it's synthesized by the protocol. If you are going to use it you have to use `decodeIfPresent` for the optional struct members. – vadian Nov 02 '18 at 17:14
  • Ok - I think you or another SO user suggested `init` in another answer when the JSON was empty for a specific piece of data, using something like `abstract = (try container.decode(String.self, forKey: .abstract)) ?? []`, or I guess in this case something like `abstract = (try container.decode(String.self, forKey: .abstract)) ?? ""`. But I'll refrain from using an initializer in future cases. Thanks as always for your help! – KingTim Nov 02 '18 at 17:19

0 Answers0