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