Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decode a named array of json objects in Swift

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.

like image 779
Damo Avatar asked Mar 06 '23 01:03

Damo


2 Answers

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")])

like image 67
Sharad Chauhan Avatar answered Mar 12 '23 13:03

Sharad Chauhan


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)
}
like image 38
PGDev Avatar answered Mar 12 '23 11:03

PGDev