使用混合类型和混合键控/非键控在 Swift 中解码 JSON
Posted
技术标签:
【中文标题】使用混合类型和混合键控/非键控在 Swift 中解码 JSON【英文标题】:Decoding JSON in Swift with Mixed Types and Mixed Keyed/Unkeyed 【发布时间】:2022-01-11 10:04:31 【问题描述】:我正在努力解码 Swift 5 中的 JSON 结构,它看起来像下面的简化示例。我正在努力解决两个问题。外部数组是无键的,内部数组是有键的。最重要的是,内部数组偶尔包含混合类型 String 和 Int 的数组。我可以提供几十个根本不起作用的东西,但我只会提供 JSON:
[
12,
"a": [
"orange",
10,
"purple"
],
"b": [
"red",
9,
"blue
],
"c": [
"yellow",
"green"
]
,
"string one",
"string two"
]
感谢任何想法。
【问题讨论】:
异构 JSON 数组是一种非常糟糕的做法。您可以使用具有关联值的枚举和自定义初始化程序对其进行解码。但是,如果您能够更改 JSON,请执行此操作。 我希望我能改变它,但必须忍受它。是的,这是一种可怕的做法! 带有枚举和关联值的自定义init(from decoder: Decoder)
方法是解决方案。
【参考方案1】:
可在 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 所说,在数组中具有这样的异构值并不是一个好习惯。你说在你的情况下你不能用后端开发改变它们,但我在这个答案中指出它,以防其他人有同样的问题,如果他/她可以改变它,推荐。
【讨论】:
非常感谢,这是一个非常好的解决方案。令人沮丧的是,这种东西来自 websocket 连接。我们这些接收端的人并没有太多考虑。以上是关于使用混合类型和混合键控/非键控在 Swift 中解码 JSON的主要内容,如果未能解决你的问题,请参考以下文章
将数组内容(自定义类型)写入 plist 作为 swift 中的键控数组