I have a Decodable
struct in Swift, with an array that might contain ints or a string representation of a double:
{
"result": {
"XXBTZUSD": [
[
1616662740,
"52591.9",
"52599.9",
"52591.8",
"52599.9",
"52599.1",
"0.11091626",
5
]
],
"last": 1616662920
}
}
My struct looks like this:
struct OHLCData: Decodable {
var pair: [[Double]]
var last: Int
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
static let last = CodingKeys(stringValue: "last")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
last = try container.decode(Int.self, forKey: .last)
let key = container.allKeys.first(where: { $0.stringValue != "last" } )?.stringValue
pair = try container.decode([[Double]].self, forKey: .makeKey(name: key!))
}
}
It falls over at run time because the double type in [[Double]]
doesn't correspond to what might be an int in the array.
I tried to create a typealias
but I get:
// Non-protocol, non-class type 'String' cannot be used within a protocol-constrained type
// Non-protocol, non-class type 'Double' cannot be used within a protocol-constrained type
typealias PairType = String & Double
How can I work around this?
CodePudding user response:
You could decode it using something like:
enum Either<A, B> {
case left(A)
case right(B)
}
extension Either: Codable where A: Codable, B: Codable {
struct NeitherError: Error {}
func encode(to encoder: Encoder) throws {
switch self {
case let .left(a):
try a.encode(to: encoder)
case let .right(b):
try b.encode(to: encoder)
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let a = try? container.decode(A.self) {
self = .left(a)
}
else if let b = try? container.decode(B.self) {
self = .right(b)
}
else {
throw NeitherError()
}
}
}
let input = """
[
1616662740,
"52591.9",
"52599.9",
"52591.8",
"52599.9",
"52599.1",
"0.11091626",
5
]
"""
let data = input.data(using: .utf8)!
let things = try! JSONDecoder().decode([Either<Int, String>].self, from: data)
You might want to split the Codable constraint into two extensions for Encodable and Decodable, and you might want to make a custom type which is StringifiedDouble
which only decodes if the thing is a String AND is a stringified representation of a Double, e.g.
struct StringifiedDouble: Codable {
var value: Double
struct TypeError: Error {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let str = try container.decode(String.self)
guard let value = Double(str) else {
throw TypeError()
}
self.value = value
}
}