1

I have a struct that I need to conform to 'Codable'. The struct itself must have an object within it that can also conform to 'Codable'. However, this prevents the struct from conforming to 'Codable'itself.

Example:

import Foundation

struct SignalRMessageBroadcastRequest: Codable {
    let method: String?
    let message: Codable
    let group: String?
}

Is there something I'm missing here that could allow this behavior?

Edit:

As @Sweeper pointed out, at compile time it is unknown what the type of message is. So I have to provide custom encode/decode logic so it can be resolved at runtime. To do this I used the value of the 'method' string to attempt encoding/decoding to different models. I suppose you could also just sequentially try different models until one works.

Solution:


struct SignalRMessageBroadcastRequest: Codable {
    let method: String
    let group: String?
    let message: Codable
    
    enum CodingKeys: CodingKey {
        case method, message, group
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        method = try container.decode(String.self, forKey: .method)
        group = try container.decode(String?.self, forKey: .group)
        if method == "method1" {
            message = try container.decode(PossibleObject1.self, forKey: .message)
        } else if method == "method2" {
            message = try container.decode(PossibleObject2.self, forKey: .message)
        } else {
            throw DecodingError.dataCorruptedError(forKey: .method, in: container, debugDescription: "no suitable config type found for method \(method)!")
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(method, forKey: .method)
        try container.encode(group, forKey: .method)
        if method == "method1" {
            try container.encode(message as? PossibleObject1, forKey: .message)
        } else if method == "method2" {
            try container.encode(message as? PossibleObject2, forKey: .message)
        }
    }
}
ibmaul
  • 63
  • 5
  • 1
    Just think about it. When decoding data, there are so many different types that conform to `Codable` that the decoder could create to initialise `message`. If you just say `let message: Codable`, how does the decoder know what type of thing to create? – Sweeper Jan 24 '22 at 15:04
  • I suppose it doesn't know the exact type. I am assuming that it could resolve 'message' at runtime though because, by definition, it would be decodable. Right? – ibmaul Jan 24 '22 at 15:06
  • If you are trying to parse JSON, you can try https://quicktype.io/ generator – Cy-4AH Jan 24 '22 at 15:09
  • Well, it could resolve 'message' at runtime, but you have to write the code that does that. See [my answer here](https://stackoverflow.com/questions/67119993/my-structure-does-not-conform-to-protocol-decodable-encodable-if-i-use-pro/67120403#67120403) for an example. – Sweeper Jan 24 '22 at 15:10
  • You need to tell the compiler what type `message` is or use generics – Joakim Danielson Jan 24 '22 at 15:10
  • I'm trying to send/receive JSON encoded objects. However the exact JSON object can vary. So I need something more abstract. – ibmaul Jan 24 '22 at 15:11
  • Pass to quicktype.io all variants and it will create parser for you – Cy-4AH Jan 24 '22 at 15:15
  • 1
    @ibmaul what you need is a generic structure `struct SignalRMessageBroadcastRequest: Codable {` `let message: Message` – Leo Dabus Jan 25 '22 at 02:28
  • 1
    @LeoDabus this is so so much simpler. I knew I could make methods generic but a generic struct?! Had no idea it could be done. Thank you so much! – ibmaul Jan 26 '22 at 17:38
  • @ibmaul you are welcome. Btw take a look at [`Array`](https://developer.apple.com/documentation/swift/array) declaration. It is a perfect example of a generic structure. – Leo Dabus Jan 26 '22 at 17:55

0 Answers0