Andrew Dorsett
Andrew Dorsett

Reputation: 13

Decoding array based JSON in Swift

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.

Upvotes: 1

Views: 59

Answers (1)

Rob Napier
Rob Napier

Reputation: 299703

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

Upvotes: 3

Related Questions