Thingamajig
Thingamajig

Reputation: 4465

What's the appropriate way to access an enum from Swift Decodables?

I have a really weird case where I took JSON and thought I would be able to substring all the way to the data I want to access, however, it's not working as expected.

Using QuickType, I was able to convert this JSON: https://kidsuper.fodalabs.com/wp-json/wp/v2/art

To the below.

When trying to access, it seems like I should be able to do .acf.gallery.id however once I get to acf.gallery, it says .id does not exist. This is strange but here's what it returns when I try

let temp = imageArray[indexPath.row].acf.gallery.id
Value of type 'GalleryUnion?' has no member 'id'

Just for fun I tried this one and had no luck as well:

let temp = imageArray[indexPath.row].acf.GalleryUnion.galleryElementArray
    Error

: Value of type 'Acf' has no member 'GalleryUnion'

The return when I print .acf.gallery starts like this:

Acf(company: "Season #3", 
     gallery: Optional(weddingszn.GalleryUnion.galleryElementArray([weddingszn.GalleryElement(
         id: 135, galleryID: 135, title: "1-791x1024", 
         filename: "1-791x1024.jpg", url: "https://kidsuper.fodalabs.com/wp-content/up

Full code is below for what I'm trying to parse. Any ideas?

struct Acf: Codable {
    let company: String
    let gallery: GalleryUnion?
    let tagline: String
    let featuredImg: Bool?

    enum CodingKeys: String, CodingKey {
        case company, gallery, tagline
        case featuredImg = "featured_img"
    }
}

enum GalleryUnion: Codable {
    case bool(Bool)
    case galleryElementArray([GalleryElement])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(Bool.self) {
            self = .bool(x)
            return
        }
        if let x = try? container.decode([GalleryElement].self) {
            self = .galleryElementArray(x)
            return
        }
        throw DecodingError.typeMismatch(GalleryUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for GalleryUnion"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .bool(let x):
            try container.encode(x)
        case .galleryElementArray(let x):
            try container.encode(x)
        }
    }
}

struct GalleryElement: Codable {
    let id, galleryID: Int
    let title, filename: String
    let url: String
    let alt, author, description, caption: String
    let name, date, modified: String
    let mimeType: MIMEType
    let type: TypeEnum
    let icon: String
    let width, height: Int
    let sizes: Sizes

    enum CodingKeys: String, CodingKey {
        case id = "ID"
        case galleryID = "id"
        case title, filename, url, alt, author, description, caption, name, date, modified
        case mimeType = "mime_type"
        case type, icon, width, height, sizes
    }
}

Upvotes: 1

Views: 210

Answers (3)

matt
matt

Reputation: 535325

however once I get to acf.gallery, it says .id does not exist. This is strange

No it isn't. According to your code, .gallery should be a GalleryUnion. Well, a GalleryUnion has no .id. It has no properties at all. A GalleryElement has an .id, but a GalleryUnion is not a GalleryElement. So I don't see where your surprise comes from; there is no surprise here.

A GalleryUnion has cases. You need to check which case this is. If it's . galleryElementArray you need to extract the associated value. Even then you won't have any .id, because what you will now have is still not a GalleryElement; it's an array of GalleryElements.

You could make this a lot easier on yourself by defining GalleryUnion with an extra calculated property that fetches the associated value for you:

enum GalleryUnion : Codable {
    case bool(Bool)
    case galleryElementArray([GalleryElement])
    var galleryElements : [GalleryElement]? {
        switch self {
        case .bool : return nil
        case let .galleryElementArray(arr): return arr
        }
    }
    // ... and so on
}

That would allow you, at least, to say:

act.gallery.galleryElements?.map {$0.id}

...or whatever it is you have in mind.

Upvotes: 2

MadProgrammer
MadProgrammer

Reputation: 347284

So, GalleryUnion can one of two things. It can either both .bool(_) or galleryElementArray(_). When you want access the actual underlying value, you need to determine which state it's in.

To do this in Swift, you can use a switch statement. You can then use it to gain access to the internally contained values. Maybe something similar to:

if let gallery = acf.gallery {
    switch gallery {
    case .galleryElementArray(let values):
        values.forEach {
            print($0.id)
        }
    case .bool(_):
        break
    }
}

You might like to have a read of Enumerations, look for the "Associated Values" sections

Upvotes: 1

Fabian
Fabian

Reputation: 5348

You have to use if case, guard case or switch case to unpack the enum before you drill down into the array.

if case .galleryElementArray(let x) = imageArray[indexPath.row].acf.gallery {
    print(x.first!.id)
}

Upvotes: 2

Related Questions