I have a named array of json objects which I receive via an API call.
{
"Images": [{
"Width": 800,
"Height": 590,
"Url": "https://obfuscated.image.url/image1.jpg"
}, {
"Width": 800,
"Height": 533,
"Url": "https://obfuscated.image.url/image2.jpg"
}, {
"Width": 800,
"Height": 478,
"Url": "https://obfuscated.image.url/image3.jpg"
}]
}
The objects are of type Image, which I have defined and have a decode function which can decode a single Image object. Image looks like:
struct Image : Codable {
let width: CGFloat
let height: CGFloat
let url: String
enum ImageKey: String, CodingKey {
case width = "Width"
case height = "Height"
case url = "Url"
}
init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: ImageKey.self)
width = try container.decodeIfPresent(CGFloat.self, forKey: .width) ?? 0.0
height = try container.decodeIfPresent(CGFloat.self, forKey: .height) ?? 0.0
url = try container.decodeIfPresent(String.self, forKey: .url) ?? ""
}
func encode(to encoder: Encoder) throws
{
}
}
I have a written a test for this scenario but this is where I am stumped! The test fails (naturally) and looks like this:
func testManyImages() throws {
if let urlManyImages = urlManyImages {
self.data = try? Data(contentsOf: urlManyImages)
}
let jsonDecoder = JSONDecoder()
if let data = self.data {
if let _images:[Image] = try? jsonDecoder.decode([Image].self, from: data) {
self.images = _images
}
}
XCTAssertNotNil(self.images)
}
My question is this:
How do I access the array of Images via the name "Images" or otherwise?
Thanks for reading and as always any help always appreciated.
I think your Codable structure is wrong. It should be :
struct JSONStructure: Codable {
let images: [Images]
private enum CodingKeys: String, CodingKey {
case images = "Images"
}
}
struct Images: Codable {
let width: CGFloat
let height: CGFloat
let url: String
private enum CodingKeys: String, CodingKey {
case width = "Width"
case height = "Height"
case url = "Url"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
width = try container.decodeIfPresent(CGFloat.self, forKey: .width) ?? 0.0
height = try container.decodeIfPresent(CGFloat.self, forKey: .height) ?? 0.0
url = try container.decodeIfPresent(String.self, forKey: .url) ?? ""
}
func encode(to encoder: Encoder) throws {
}
}
And then :
if let data = self.data {
if let decoded = try? jsonDecoder.decode(JSONStructure.self, from: data) {
print("Decoded : \(decoded)")
self.images = decoded.images
}
}
Logs :
Decoded : JSONStructure(images: [DreamSiteradio.Images(width: 800.0, height: 590.0, url: "https://obfuscated.image.url/image1.jpg"), DreamSiteradio.Images(width: 800.0, height: 533.0, url: "https://obfuscated.image.url/image2.jpg"), DreamSiteradio.Images(width: 800.0, height: 478.0, url: "https://obfuscated.image.url/image3.jpg")])
You can create 2 structs instead of 1 for parsing the complete response, i.e.
struct Response: Codable
{
let images: [Image]?
enum CodingKeys: String, CodingKey
{
case images = "Images"
}
}
struct Image : Codable
{
var width: CGFloat?
let height: CGFloat?
let url: String?
enum CodingKeys: String, CodingKey
{
case width = "Width"
case height = "Height"
case url = "Url"
}
}
Since there are nested levels in the response, this is why I've created multiple structs.
Also you don't need to write explicit container for parsing. Codable
will do it on its own.
You can simply decode the sample JSON
with:
if let data = jsonStr.data(using: .utf8)
{
let response = try? JSONDecoder().decode(Response.self, from: data)
print(response?.images)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With