Decoding array based JSON in Swift

Viewed 39

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 Answers

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