Search code examples
jsonswiftjsondecoder

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.


Solution

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