2

Using Swift4, iOS11.1, Xcode9.1,

Trying to match the JSON-file in a decodable struct for Swift4, I end up with the following error message:

"Expected to decode Array<Any> but found a dictionary instead." --> After finding two typo's - there is still something wrong!

The error-message now says: The Error-Messages now says: intValue: Optional(5))], debugDescription: "No value associated with key photos (\"photos\").", underlyingError: nil))

The Coordinates-Initializer inside the Coordinates-Struct (see code below), I got form the following post. Everything else I tried to match exactly with the JSON-File (see at very bottom)...

For now, I don't see any typos - could there be something else I am missing ?

The JSON-File is fetched as follows. Is there maybe anything wrong here ?

    let myTask = session.dataTask(with: myRequest) { (data, response, error) in

        if (error != nil) {
            print("Error1 fetching JSON data")
        }
        else {
            do {
                //Decode retrived data with JSONDecoder and assing type of Station object
                let stationData = try JSONDecoder().decode(Station.self, from: data!)

                //Get back to the main queue
                DispatchQueue.main.async {
                    print(stationData)
                }
            }
            catch let error {
                print(error)
            }
        }
    }
    myTask.resume()

The struct looks as follows (see JSON below):

struct Station: Codable {

    let htmlAttributions: [String]
    let nextPageToken: String
    let results: [Result]
    let status: String

    struct Result: Codable {
        let formattedAddress: String
        let geometry: Geometry
        let icon: String
        let id: String
        let name: String
        let photos: [Photo]
        let placeID: String
        let rating: Double
        let reference: String
        let types: [String]

        struct Geometry: Codable {
            let location: Coordinates
            let viewport: Viewport

            struct Coordinates: Codable {
                let lat: Double
                let lng: Double
                init(from decoder: Decoder) throws {
                    let values = try decoder.container(keyedBy: CodingKeys.self)
                    lat = try values.decode(Double.self, forKey: .lat)
                    lng = try values.decode(Double.self, forKey: .lng)
                }
                enum CodingKeys : String, CodingKey {
                    case lat
                    case lng
                }
            }

            struct Viewport: Codable {
                let northeast: Coordinates
                let southwest: Coordinates

                enum CodingKeys : String, CodingKey {
                    case northeast
                    case southwest
                }
            }

            enum CodingKeys : String, CodingKey {
                case location
                case viewport
            }
        }
        // !!!!!!!!!!!! something wrong here ???? !!!!!!!!!!!
        struct Photo: Codable {

            let height: Int
            let htmlAttributions: [String]
            let photoReference: String
            let width: Int
            enum CodingKeys : String, CodingKey {
                case height
                case htmlAttributions = "html_attributions"
                case photoReference = "photo_reference"
                case width
            }
        }

        enum CodingKeys : String, CodingKey {
            case formattedAddress = "formatted_address"
            case geometry
            case icon
            case id
            case name
            case photos
            case placeID = "place_id"
            case rating
            case reference
            case types
        }
    }

    enum CodingKeys : String, CodingKey {
        case htmlAttributions = "html_attributions"
        case nextPageToken = "next_page_token"
        case results
        case status
    }
}

Here is the JSON File:

{
   "html_attributions" : [],
   "next_page_token" : "F3ddaOzOcyo94AA2skDm",
   "results" : [
      {
         "formatted_address" : "Strasse 1, 6003 Luzern, Switzerland",
         "geometry" : {
            "location" : {
               "lat" : 47.04951260000001,
               "lng" : 8.310404999999999
            },
            "viewport" : {
               "northeast" : {
                  "lat" : 47.0508615802915,
                  "lng" : 8.311753980291503
               },
               "southwest" : {
                  "lat" : 47.0481636197085,
                  "lng" : 8.309056019708498
               }
            }
         },
         "icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png",
         "id" : "a3d600a6e78105b6ce2b5f5a3fac98ca1910a09b",
         "name" : "Luzern",
         "photos" : [
            {
               "height" : 4000,
               "html_attributions" : [
                  "\u003ca href=\"https://maps.google.com/maps/contrib/113951418385089253589/photos\"\u003eAlex Marcu\u003c/a\u003e"
               ],
               "photo_reference" : "CmRaAAAAYHK1VHDFlkbzXuMnF2MLEdew-36lgHC2lS1Cxg_DELgP-ckZH7G6aa-81LGDpR5rPZY1XMw64mytsjXIrdB5n3QQmXjGgelwZEbHaetT2jpy9SeaHDH3qUGGAUW-7BtZEhCxXy2dxGSv6A_g7fipsCr5GhRZlPuliykokXIkqfqIN_vMWzmYyA",
               "width" : 3000
            }
         ],
         "place_id" : "ChIJqQIbhpj7j0cRjUguIM__gZw",
         "rating" : 4.4,
         "reference" : "CmRSAAAAzBZCshpypXcbMhrBQIdK2zISd3Q40QRSFO0KKhIrTejnGiZIoASuVqCVtmNBnFsodLWrYtOP-RmwCqDBDVbMheeCbFk7f0L8gwixLx_SGhYTDqPd6B2IwPWWXH5Pb6lxEhBoQtWj-kB-g1ZiOZ74hswNGhSd9Kf9Qj1P2_fdQCTO_VCoTU09JA",
         "types" : [
            "transit_station",
            "bus_station",
            "train_station",
            "point_of_interest",
            "establishment"
         ]
      },
      { ...more results... },
      { ...more results... }
   ],
   "status" : "OK"
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
iKK
  • 6,394
  • 10
  • 58
  • 131
  • Could you please post the full error message? It should also say at which point the decoding failed. – Hamish Nov 28 '17 at 16:21
  • I did find two typo's and updated the post accordingly - but still, there is something wrong with the Photos-Struct. The Error-Messages now says: `intValue: Optional(5))], debugDescription: "No value associated with key photos (\"photos\").", underlyingError: nil))` – iKK Nov 28 '17 at 16:44
  • Thanks, but you're still cutting off the first half of the error message – what does that say? – Hamish Nov 28 '17 at 17:00
  • Here is the full error-message from the log: `keyNotFound(MyApp.Station.Result.CodingKeys.photos, Swift.DecodingError.Context(codingPath: [MyApp.Station.CodingKeys.results, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 5", intValue: Optional(5))], debugDescription: "No value associated with key photos (\"photos\").", underlyingError: nil))` – iKK Nov 28 '17 at 17:02
  • Thank you, that helps already (I am learning). But isn't the 6th element the "photos" element and its key is as well "photos" ?? Or am I missing something. What is to do in this case ? – iKK Nov 28 '17 at 17:11
  • I wrote an answer. The 6th element is one of the `{ ...more results... }` – vadian Nov 28 '17 at 17:13

2 Answers2

2

The error message says that the 6th element (index 5) of the results array does not have a key photos.

Easy solution is to declare the array as optional

let photos: [Photo]?

Extensive solution is to add an initializer to be able to assign a default non-optional value.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thank you for your post! Do I have to set every element as Optional ? Since after putting `[Photo]?` as you suggested, the error message went to brag about index6 (i.e element-7). But there placing a `?` does not help... Can you please explain a bit more when to place a ? and when not ? And what could be wrong now with index6 ??? – iKK Nov 28 '17 at 17:17
  • Please read the error message carefully *intValue: 5 ... No value associated with key foo* means that the 6th dictionary (indices are zero-based) has no key `foo` and you have to make the corresponding property in the struct optional to avoid that error. – vadian Nov 28 '17 at 17:24
  • Thanks a lot, vadian, for your help on this. It was again a learning to read carefully the error message ;). Can you maybe answer whether it is good practice to set all the Struct-entries to be an `optional` ? (since it is not known whether the JSON really contains the value. Or what does the `?` in this context really mean ? – iKK Nov 28 '17 at 17:33
  • The question mark means that the property is an optional (can be `nil`). Personally I'm not a supporter of declaring all properties thoughtlessly as optional. Optionals are not an alibi for *don't-care*. – vadian Nov 28 '17 at 17:42
  • Thanks - therefore one must know which JSON-entries might be missing and which not. Strange enough: With all Struct-entries set to optional, I receive only one element (instead of 20). Don't know why yet... Thanks for your splendid help !! – iKK Nov 28 '17 at 17:48
  • Supplement: Please watch [WWDC 2017 Videos: What's new in Foundation](https://developer.apple.com/videos/play/wwdc2017/212/). It provides a comprehensive overview of the `Coding` protocol – vadian Nov 29 '17 at 10:08
  • Thank you vadian! The issue with just one element shown is solved - it was an InfoPlist.strings localization issue (see [link](https://stackoverflow.com/questions/47541175/missing-data-with-urlsession-and-jsondecode-in-swift4)). And thanks for the video-link - I'll have a look at it! – iKK Nov 29 '17 at 11:06
1

With the help of vadian, I finally have found my matching Struct. Thanks a lot vadian!

struct Station: Codable {

    let htmlAttributions: [String]
    let nextPageToken: String
    let results: [Result]
    let status: String

    struct Result: Codable {
        let formattedAddress: String
        let geometry: Geometry
        let icon: String
        let id: String
        let name: String
        let photos: [Photo]?
        let placeID: String
        let rating: Double?
        let reference: String
        let types: [String]

        struct Geometry: Codable {
            let location: Coordinates
            let viewport: Viewport

            struct Coordinates: Codable {
                let lat: Double
                let lng: Double
                init(from decoder: Decoder) throws {
                    let values = try decoder.container(keyedBy: CodingKeys.self)
                    lat = try values.decode(Double.self, forKey: .lat)
                    lng = try values.decode(Double.self, forKey: .lng)
                }
                enum CodingKeys : String, CodingKey {
                    case lat
                    case lng
                }
            }

            struct Viewport: Codable {
                let northeast: Coordinates
                let southwest: Coordinates

                enum CodingKeys : String, CodingKey {
                    case northeast
                    case southwest
                }
            }

            enum CodingKeys : String, CodingKey {
                case location
                case viewport
            }
        }

        struct Photo: Codable {

            let height: Int
            let htmlAttributions: [String]
            let photoReference: String?
            let width: Int
            enum CodingKeys : String, CodingKey {
                case height
                case htmlAttributions = "html_attributions"
                case photoReference = "photo_reference"
                case width
            }
        }

        enum CodingKeys : String, CodingKey {
            case formattedAddress = "formatted_address"
            case geometry
            case icon
            case id
            case name
            case photos
            case placeID = "place_id"
            case rating
            case reference
            case types
        }
    }

    enum CodingKeys : String, CodingKey {
        case htmlAttributions = "html_attributions"
        case nextPageToken = "next_page_token"
        case results
        case status
    }
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
iKK
  • 6,394
  • 10
  • 58
  • 131
  • Why do you need this line ? init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) lat = try values.decode(Double.self, forKey: .lat) lng = try values.decode(Double.self, forKey: .lng) } Your code should works without this. – Jamshed Alam Apr 26 '18 at 08:49
  • In fact, this `init(from decoder: ....)` is no longer needed !! It was needed as for earlier Swift-versions. But since Swift4 you can leave it away ! Also, I normally use the service [json4swift](http://www.json4swift.com/) (which is great and delivers the struct back from your json entry) - however they somehow did not update fully to Swift4, I think, at least their structs keep the `init(from decoder: ....)` for some reason. And that's how it sneaked into my code ;) Again, just leave it aside and it will work ! – iKK Apr 27 '18 at 08:15