0

Imgur image search response has following structure (simplified) :

{ "data": [  {"title" : "Kittens",
                "images" : [ { "title" : "",
                               "descripton" : "",
                               "nsfw" : "",
                               "link" : "https:\/\/i.imgur.com\/L1olKr1.jpg"
                          }, { "title" : "",
                                "descripton" : "",
                                "nsfw" : "",
                                "link" : "https:\/\/i.imgur.com\/L1olKr1.jpg" }]
                },{"title" : "Kittens",
                   "images" : [ { "title" : "",
                                  "descripton" : "",
                                  "nsfw" : "",
                                  "link" : "https:\/\/i.imgur.com\/L1olKr1.jpg"
                              }, { "title" : "",
                                   "descripton" : "",
                                   "nsfw" : "",
                                   "link" : "https:\/\/i.imgur.com\/L1olKr1.jpg" }]
          }]
   }

The goal is to create a model for it. My attempt:

struct RawServerResponse: Decodable {
    
    // MARK: - ImageCategoy
    struct ImageCategory: Codable {
        let title: String
        let images: [Image]
    }

    // MARK: - Image
    struct Image: Codable {
        let title, descripton, nsfw: String
        let link: String
    }
    
    let data: [ImageCategory]
}

// We need to be getting an array of [ImageModel] as a result of decoding.

// Flattens nested json model to get data that only we need:
struct ImageModel: Decodable {
    let title: String
    let description: String
    let nsfw: String
    let link: String
    
    init(from decoder: Decoder) throws {
        let rawResponse = try RawServerResponse(from: decoder)
        
        title = rawResponse.data.first!.title
        description = rawResponse.data.first!.images.first!.descripton
        nsfw = rawResponse.data.first!.images.first!.nsfw
        link = rawResponse.data.first!.images.first!.link
        
    }

Calling that doesn't work:

  let decodedImageModel : ImageModel = try JSONDecoder().decode(ImageModel.self, from: data)

I need to be getting an array of [ImageModel]. Pls let me know if there is a simpler way of getting images out of imgur response.


UPDATE 1: Final answer incorporating @Gereon 's response. Note: some of the values in the json ended up having null instead of Strings. To handle that case, needed to add 'init' method that gave empty strings to values that weren't present:

import Foundation

struct ImgurResponse: Codable {
    let data: [ImageData]
}

struct ImageData: Codable {
    let title: String
    let images: [Image]
}

struct Image: Codable {
    let title, description, nsfw: String
    let link: String
    
    enum CodingKeys: String, CodingKey {
        case title
        case description
        case nsfw
        case link
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
        description = try container.decodeIfPresent(String.self, forKey: .description) ?? ""
        nsfw = try container.decodeIfPresent(String.self, forKey: .nsfw) ?? ""
        link = try container.decodeIfPresent(String.self, forKey: .link) ?? ""
    }
}

UPDATE 2:

Alternatively, as suggested by @Gereon, you can mark the corresponding properties as Optional, eg let link: String?. This allows you to get rid of the manual CodingKeys and init implementation.

4
  • There are no images in your JSON. It’s all strings. Commented Feb 21, 2021 at 6:27
  • Decode the JSON as a RawServerResponse and stop. Commented Feb 21, 2021 at 6:29
  • @matt Now i understand. I don't need to create ImageModel since 'Image' has all i need. Quick question: In case of a value coming in as null, is there any way of handling it besides of implementing CodingKeys and init() ? Commented Feb 21, 2021 at 10:20
  • 1
    You can mark the corresponding properties as Optional, eg let link: String?. This allows you to get rid of the manual CodingKeys and init implementation again Commented Feb 22, 2021 at 6:25

2 Answers 2

2

There is no need to create a duplicate ImageModel struct, you can extract the desired information directly from the API response:

struct ImgurResponse: Codable {
    let data: [ImageData]
}

struct ImageData: Codable {
    let title: String
    let images: [Image]
}

struct Image: Codable {
    let title, descripton, nsfw: String
    let link: String
}

let response = try JSONDecoder().decode(ImgurResponse.self, from: data)

let images = response.data.flatMap { $0.images.compactMap { $0 }} // [Image]
Sign up to request clarification or add additional context in comments.

3 Comments

I have a follow up question: How would you assign 'title' from ImageData to Image's title ? Is there a way using higher order functions?
Please ask this as a seperate question
0

Model

  struct RawServerResponse: Decodable {
        
        // MARK: - ImageCategoy
        struct ImageCategory: Codable {
            let title: String
            let images: [Image]
        }
    
        // MARK: - Image
        struct Image: Codable {
            let title, descripton, nsfw: String
            let link: String
        }
        
        let data: [ImageCategory]
    }
    
    // We need to be getting an array of [ImageModel] as a result of decoding.
    
    // Flattens nested json model to get data that only we need:
    struct ImageModel: Decodable {
        let title: String
        let description: String
        let nsfw: String
        let link: String
        
        
        init(title: String, desc: String, nsfw: String, link: String) {
            self.title = title
            self.description = desc
            self.nsfw = nsfw
            self.link = link
        }
    }
    
    struct ImageModels: Decodable {
        let arrObj:[ImageModel]
        
        init(from decoder: Decoder) throws {
            let rawResponse = try RawServerResponse(from: decoder)
            arrObj  = rawResponse.data.compactMap{ ImageModel(title: $0.title, desc: $0.images.first!.descripton, nsfw: $0.images.first!.nsfw, link: $0.images.first!.link)}
            /*var arr: [ImageModel] = []
            for imagecat in rawResponse.data{
                let model = ImageModel(title: imagecat.title, desc: imagecat.images.first!.descripton, nsfw: imagecat.images.first!.nsfw, link: imagecat.images.first!.link)
                arr.append(model)
            }
            
            arrObj = arr*/
        }
    }

Parsing

do{
                let serverData = try Data(contentsOf: data)
                let decodedImageModel : ImageModels = try JSONDecoder().decode(ImageModels.self, from: serverData)
                print("Response Success: \(decodedImageModel)")
            }catch{
                print("Error Data: \(error.localizedDescription)")
            }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.