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

Viewed 37

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!

1 Answers

@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)
    }
}
Related