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.
let link: String?. This allows you to get rid of the manualCodingKeysandinitimplementation again