JSON
SO
Cases
Case 1
To decode JSON that represents the array of heterogeneous elements as a struct with fields that hold those elements, use UnkeyedDecodingContainer
and iterate over fields with func decode()
. Each call of decode()
moves iteration to the next element, make sure to decode all fields by checking the count
or isAtEnd
properties.
import Foundation
/// `Payload`:
///
/// [
/// 0,
/// "XBT/USD"
/// ]
///
struct Message {
let channelID: Int
let pair: String
}
extension Message: Decodable {
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
let channelID = try container.decode(Int.self)
let pair = try container.decode(String.self)
guard container.isAtEnd else {
throw ThereIsMoreToDecodeError()
}
self.init(channelID: channelID, pair: pair)
}
struct ThereIsMoreToDecodeError: Error {}
}
Run to test
let payload = """
[
0,
"XBT/USD"
]
"""
let data = Data(payload.utf8)
let decoder = JSONDecoder()
let message = try decoder.decode(Message.self, from: data)
Case 2
To decode heterogeneous array where elements have common and different parts use struct with enum field for changing part and non-enum for common part.
import Foundation
/// `Payload`
///
/// [
/// {
/// "channelID": 10001,
/// "channelName": "ticker",
/// "event": "subscriptionStatus"
/// },
/// {
/// "errorMessage": "Subscription depth not supported",
/// "channelName": "ticker",
/// "event": "subscriptionStatus"
/// }
/// ]
struct Message {
typealias Name = String
typealias Event = String
let oneOf: OneOf
let name: Name
let event: Event
}
extension Message: Decodable {
enum CodingKeys: String, CodingKey {
case name, event
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let oneOf = try container.decode(OneOf.self)
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
let name = try keyedContainer.decode(Name.self, forKey: .name)
let event = try keyedContainer.decode(Event.self, forKey: .event)
self.init(oneOf: oneOf, name: name, event: event)
}
}
enum OneOf: Decodable {
case errorMessage(ErrorMessage)
case channelID(ChannelID)
struct ErrorMessage: Decodable {
let errorMessage: String
}
struct ChannelID: Decodable {
let channelID: Int
}
enum CodingKeys: String, CodingKey {
case errorMessage, channelID
}
init(from decoder: Decoder) throws {
do {
let errorMessage = try ErrorMessage(from: decoder)
self = .errorMessage(errorMessage)
} catch {
let channelID = try ChannelID(from: decoder)
self = .channelID(channelID)
}
}
}
Run to test
let payload = """
[
{
"channelID": 10001,
"name": "ticker",
"event": "subscriptionStatus"
},
{
"errorMessage": "Subscription depth not supported",
"name": "ticker",
"event": "subscriptionStatus"
}
]
"""
let data = Data(payload.utf8)
let decoder = JSONDecoder()
let messages = try decoder.decode([Message].self, from: data)
Published on: Feb 7, 2022