可在 Playground 中使用的可能解决方案:
func heterogenousJSON() {
let jsonStr = """
[
12,
{
"a": [
"orange",
10,
"purple"
],
"b": [
"red",
9,
"blue"
],
"c": [
"yellow",
"green"
]
},
"string one",
"string two"
]
"""
struct CodableStruct: Codable, CustomStringConvertible {
let a: [CodableStructValues]
let b: [CodableStructValues]
let c: [String] //Here I set it as [String], but could be indeed [CodableStructValues], just to make it more "usual case"
var description: String {
"{ \"a\": [\(a.map{ $0.description }.joined(separator: ", "))] }\n" +
"{ \"b\": [\(b.map{ $0.description }.joined(separator: ", "))] }\n" +
"{ \"c\": [\(c.map{ $0 }.joined(separator: ", "))] }"
}
}
enum CodableStructValues: Codable, CustomStringConvertible {
case asInt(Int)
case asString(String)
init(from decoder: Decoder) throws {
let values = try decoder.singleValueContainer()
if let asInt = try? values.decode(Int.self) {
self = .asInt(asInt)
return
}
//For the next: we didn't use `try?` but try, and it will throw if it's not a String
// We assume that if it wasn't okay from previous try?, it's the "last chance". It needs to be of this type, or it will throw an error
let asString = try values.decode(String.self)
self = .asString(asString)
}
var description: String {
switch self {
case .asInt(let intValue):
return "\(intValue)"
case .asString(let stringValue):
return stringValue
}
}
}
enum Heterogenous: Codable {
case asInt(Int)
case asString(String)
case asCodableStruct(CodableStruct)
init(from decoder: Decoder) throws {
let values = try decoder.singleValueContainer()
if let asInt = try? values.decode(Int.self) {
self = .asInt(asInt)
return
} else if let asString = try? values.decode(String.self) {
self = .asString(asString)
return
}
//For the next: we didn't use `try?` but try, and it will throw if it's not a String
// We assume that if it wasn't okay from previous try?, it's the "last chance". It needs to be of this type, or it will throw an error
let asStruct = try values.decode(CodableStruct.self)
self = .asCodableStruct(asStruct)
}
}
do {
let json = Data(jsonStr.utf8)
let parsed = try JSONDecoder().decode([Heterogenous].self, from: json)
print(parsed)
parsed.forEach { aHeterogenousParsedValue in
switch aHeterogenousParsedValue {
case .asInt(let intValue):
print("Got Int: \(intValue)")
case .asString(let stringValue):
print("Got String: \(stringValue)")
case .asCodableStruct(let codableStruct):
print("Got Struct: \(codableStruct)")
}
}
} catch {
print("Error while decoding JSON: \(error)")
}
}
heterogenousJSON()
主要思想是使用 Codable enum with associated values 来保存所有异构值。然后你需要有一个自定义的init(from decoder: Decoder)。我创建了Codable 的值,但实际上我只创建了Decodable 部分。没有反向覆盖。
我使用CustomStringConvertible(及其description)来获得更易读的打印。
我在打印parsed 时添加了forEach(),以向您展示如何处理这些值。如果只需要一种情况,可以使用if case let 代替开关。
正如@vadian in comments 所说,在数组中具有这样的异构值并不是一个好习惯。你说在你的情况下你不能用后端开发改变它们,但我在这个答案中指出它,以防其他人有同样的问题,如果他/她可以改变它,推荐。