Eugene Berezin
Eugene Berezin

Reputation: 49

JSON decoding in Swift. The data couldn’t be read because it isn’t in the correct format

I am having trouble decoding my JSON. It says: "The data couldn’t be read because it isn’t in the correct format." I can't put my finger on it what's wrong. Would you mind to take a pick?

Endpoint: https://images-api.nasa.gov/search?q=apollo%2011&media_type=video

JSON Sample:

{
"collection": {
    "version": "1.0",
    "href": "https://images-api.nasa.gov/search?q=apollo%2011&media_type=video",
    "items": [
        {
            "href": "https://images-assets.nasa.gov/video/Apollo 11 Overview/collection.json",
            "links": [
                {
                    "href": "https://images-assets.nasa.gov/video/Apollo 11 Overview/Apollo 11 Overview~thumb.jpg",
                    "render": "image",
                    "rel": "preview"
                },
                {
                    "href": "https://images-assets.nasa.gov/video/Apollo 11 Overview/Apollo 11 Overview.srt",
                    "rel": "captions"
                }
            ],
            "data": [
                {
                    "description": "Video highlights from the historic first manned landing on the moon, during the Apollo 11 mission in July 1969.",
                    "date_created": "2013-05-15T00:00:00Z",
                    "media_type": "video",
                    "keywords": [
                        "Apollo 11",
                        "Moon"
                    ],
                    "nasa_id": "Apollo 11 Overview",
                    "center": "HQ",
                    "title": "Apollo 11 Overview"
                }
            ]
        },

My model:

struct NasaCollection: Codable {
    var collection: Collection
}

// MARK: - Collection
struct Collection: Codable {
    let version: String
    let href: String
    let items: [Item]
}

// MARK: - Item
struct Item: Codable {
    let href: String
    let links: [ItemLink]
    let data: Datum
    
}

// MARK: - ItemLink
struct ItemLink: Codable {
    let href: String
    let render: Render?
    
    
}

// MARK: - Datum
struct Datum: Codable {
    let datumDescription: String
    let dateCreated: Date
    let keywords: [String]
    let nasaID: String
    let title: String
    let location, description508, photographer, secondaryCreator: String?
    let album: [String]?

}

enum Render: String, Codable {
    case image = "image"
}

// MARK: - CollectionLink
struct CollectionLink: Codable {
    let prompt, rel: String
    let href: String
}

// MARK: - Metadata
struct Metadata: Codable {
    let totalHits: Int

    enum CodingKeys: String, CodingKey {
        case totalHits = "total_hits"
    }
}

Decding:

func getVideos(completed: @escaping (Result<[Item], Error>)-> Void) {
        let endpoint = "https://images-api.nasa.gov/search?q=apollo%2011&media_type=video"
        
        guard let url = URL(string: endpoint) else { return }
        
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let _ = error {
                completed(.failure(error?.localizedDescription as! Error))
            }
            
            guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                completed(.failure(error?.localizedDescription as! Error))
                return
            }
            
            guard let data = data else {
                completed(.failure(error?.localizedDescription as! Error))
                return
            }
            
            do {
                let decoder = JSONDecoder()
                decoder.dataDecodingStrategy = .base64
                let videos = try decoder.decode([Item].self, from: data)
                completed(.success(videos))

            } catch {
                print(error.localizedDescription)

            }
        
        }
        task.resume()
        
    }
    

I think my model is correct and it "should" be decoded. I tried to decode the very root of JSON and still getting the same error.

Upvotes: 0

Views: 293

Answers (2)

gcharita
gcharita

Reputation: 8327

First you need to change data type from Datum to array [Datum], in Item, as @emrcftci mentioned in his answer:

struct Item: Codable {
    let href: String
    let links: [ItemLink]
    let data: [Datum]
}

Then you need to change nasaID property to nasaId and dateCreated type from Date to String, in Datum:

struct Datum: Codable {
    let description: String
    let dateCreated: String
    let keywords: [String]
    let nasaId: String
    let title: String
    let location, description508, photographer, secondaryCreator: String?
    let album: [String]?
}

And finally when you decoding, pass .convertFromSnakeCase to keyDecodingStrategy property of JSONDecoder and use NasaCollection.self as type when calling decode(_:from:) function, instead of [Item].self:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
    let decoded = try decoder.decode(NasaCollection.self, from: data)
    print(decoded)
} catch {
    print(error)
}

Upvotes: 1

emrcftci
emrcftci

Reputation: 3516

Item's data should be an array of Datum -> [Datum]

As you can see the response ->

{
"collection": {
    "version": "1.0",
    "href": "https://images-api.nasa.gov/search?q=apollo%2011&media_type=video",
    "items": [
        {
            "href": "https://images-assets.nasa.gov/video/Apollo 11 Overview/collection.json",
            "links": [
                {
                    "href": "https://images-assets.nasa.gov/video/Apollo 11 Overview/Apollo 11 Overview~thumb.jpg",
                    "render": "image",
                    "rel": "preview"
                },
                {
                    "href": "https://images-assets.nasa.gov/video/Apollo 11 Overview/Apollo 11 Overview.srt",
                    "rel": "captions"
                }
            ],
            "data": [ // <------ Data has an array of an object(Datum).
                {
                    "description": "Video highlights from the historic first manned landing on the moon, during the Apollo 11 mission in July 1969.",
                    "date_created": "2013-05-15T00:00:00Z",
                    "media_type": "video",
                    "keywords": [
                        "Apollo 11",
                        "Moon"
                    ],
                    "nasa_id": "Apollo 11 Overview",
                    "center": "HQ",
                    "title": "Apollo 11 Overview"
                }
            ]
        },

You should set data as an array [Datum]

struct Item: Codable {
    let href: String
    let links: [ItemLink]
    let data: [Datum]
}

And you should update Datum object as following

{
    "description": "Video highlights from the historic first manned landing on the moon, during the Apollo 11 mission in July 1969.",
    "date_created": "2013-05-15T00:00:00Z",
    "media_type": "video",
    "keywords": [
        "Apollo 11",
        "Moon"
    ],
    "nasa_id": "Apollo 11 Overview",
    "center": "HQ",
    "title": "Apollo 11 Overview"
}
struct Datum: Codable {
    let description: String
    let dateCreated: Date
    let keywords: [String]
    let nasaID: String
    let title: String
    let location, description508, photographer, secondaryCreator: String?
    let album: [String]?

    enum CodingKeys: String, CodingKey {
        case dateCreated = "date_created"
        case nasaID = "nasa_id"
    }
}

BONUS

You are trying to parse NasaCollection instead of [Item]. Because of that in getVideos(completed:) function your do block should be like this ->

do {
    let decoder = JSONDecoder()
    decoder.dataDecodingStrategy = .base64

    // You should try to decode `NasaCollection`!!!
    let videos = try decoder.decode(NasaCollection.self, from: data)
    completed(.success(videos))
}

Upvotes: 0

Related Questions