1

I've got a WebSocket that uses an array format of [command, data] to exchange messages. I'm struggling with the Swift Decoder to handle the mixed formats of the data portion. For instance Message #1:

["CFG",{"dimmerDelay":5000,"keyboardShortcuts":true}]

Message #2:

["LFM",true]

I'm using this to decode Message #1

struct Message: Decodable {
    let command: String
    let config: Config
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        command = try container.decode(String.self)
        config = try container.decode(Config.self)
        }
    }

struct Config: Decodable {
    let dimmerDelay: Int
    let keyboardShortcuts: Bool
    }

What I really want is to split this into a container more like this:

struct Message: Decodable {
    let command: String
    let data: String
    }

Then if the message is of type "CFG" I would pass the data to a decoder that would create the Config object. If the message is of type "LFM" I would check to ensure the result is true and if the message is of another type, I'd pass that data to another decoder that would create the relevant decoded objects.

1 Answer 1

3

There are several approaches, but assuming you know all the "command" strings and what they map to, you can take an enum approach:

enum Message {
    case config(Config)
    case lfm(Bool)
}

To make this Decodable, you just need a switch statement:

extension Message: Decodable {
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        let command = try container.decode(String.self)

        switch command {
        case "CFG":
            self = .config(try container.decode(Config.self))

        case "LFM":
            self = .lfm(try container.decode(Bool.self))

        default:
            throw DecodingError.typeMismatch(Message.self,
                                             .init(codingPath: [],
                                                   debugDescription: "Unknown command: \(command)"))
        }
    }
}

After decoding, you'll use a switch statement to determine what you have:

let message = try JSONDecoder().decode(Message.self, from: json)

switch message {
case .config(let config): handleConfig(config)
case .lfm(let bool): handleLFM(bool)
}
Sign up to request clarification or add additional context in comments.

3 Comments

So yes I have an enum of all the commands: enum WebsocketCommand: String { case Call = "CAL" case Config = "CFG" case ListCall = "LCL" case LivefeedMap = "LFM" case Pin = "PIN" } Now how do I determine what the message type is? So I receive the message and I decode using this, what's the right way to determine if resulting Message type is a Config vs LFM vs LCL, etc
Updated with the switch statement you'd need.
Wow that is really elegant and so much easier than what I was trying to do! It works great! Thanks!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.