iosswiftcodableexistential-type

What's the best way to encode/decode a heterogenous array of type [any TypeName] in Swift 5.7?


So I have a protocol with roughly this structure:

protocol Content: Codable {

    var type: ContentType { get }    
    associatedtype ContentData: Codable
    var data: ContentData { get set }
    var id: UUID { get }

    ...
}

and I have an enum like this:

enum ContentType: String, Equatable, CaseIterable, Codable, RawRespresentable {
    case type1 = "Type 1"
    case type2 = "Type 2"
}

And then I have an object using Swift's new 5.7 syntax that holds the mixed types of Content like this:

class ContentCollection: Codable {
    var contents: [any Content]
    ...
}

So I'm able to decipher which type of content it is from the type property on any piece of any Content and properly type cast it like this:

for content in contents {
    switch content.type {
        case .type1:
            let typedContent = content as! Type1 
            try container.encode(typedContent, forKey: .contents)      
        case .type2:
            let typedContent = content as! Type2       
            try container.encode(typedContent, forKey: .contents)
        ...
    }
}

But that just writes the single piece of content over the whole array. How do you encode a single piece at a time and add it to the JSON array? I'm fairly new to Codable so forgive me if I"m missing something obvious.

And similarly, how would you decode it back?

Thanks!


Solution

  • @Sweeper answered it.

    For decoding I did this:

    var contentsContainer = try container.nestedUnkeyedContainer(forKey: .contents)
            
    var contents: [any Content] = []
    while !contentsContainer.isAtEnd {
        // try to decode as every type of Content
        contents.append(try contentsContainer.decode(ContentType1.self))
        contents.append(try contentsContainer.decode(ContentType2.self))
        contents.append(try contentsContainer.decode(ContentType3.self))
    }
            
    self.contents = contents
    

    and encoding I did this:

    var contentsContainer = container.nestedUnkeyedContainer(forKey: .contents)
                    
    for content in contents {
        switch content.type {
        case .contentType1:
            try contentsContainer.encode(content as! ContentType1)
        case .contentType2:
            try contentsContainer.encode(content as! ContentType2)
        case .contentType3:
            try contentsContainer.encode(content as! ContentType3)
        }
    }