当 Swift 中的 JSON 是一个数组并且第一项与其余项的类型不同时,我该如何解码?
How do I decode JSON in Swift when it's an array and the first item is a different type than the rest?
假设 JSON 看起来像这样:
[
{
"name": "Spot",
"breed": "dalmation"
},
{
"color": "green",
"eats": "lettuce"
},
{
"color": "brown",
"eats": "spinach"
},
{
"color": "yellow",
"eats": "cucumbers"
}
]
您从 API 返回的 JSON 响应中的第一项始终是狗,之后的所有项始终是乌龟。所以项目 0 是狗,项目 1 到 N-1 是乌龟。
如何将其解析为我可以阅读的内容,例如:
struct Result: Codable {
let dog: Dog
let turtles: [Turtle]
}
可能吗?
您可以为您的 Result
结构实现自定义解码器。
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
// Assume the first one is a Dog
self.dog = try container.decode(Dog.self)
// Assume the rest are Turtle
var turtles = [Turtle]()
while !container.isAtEnd {
let turtle = try container.decode(Turtle.self)
turtles.append(turtle)
}
self.turtles = turtles
}
只需少量工作,您就可以支持 Dog
词典位于 Turtle
词典数组中的任何位置。
既然你声明你的结构是 Codable 而不仅仅是 Decodable,你也应该从 Encodable 实现自定义 encode(to:)
但这是留给 reader.
的练习
因此您的 Array
包含两种类型的元素。这是 Type1OrType2
问题的一个很好的例子。对于这种情况,您可以考虑使用 enum
和关联类型。对于您的情况,您需要一个 Codable
枚举和 init(from:) throws
& func encode(to:) throws
的自定义实现
enum DogOrTurtle: Codable {
case dog(Dog)
case turtle(Turtle)
struct Dog: Codable {
let name: String
let breed: String
}
struct Turtle: Codable {
let color: String
let eats: String
}
}
extension DogOrTurtle {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
// First try to decode as a Dog, if this fails then try another
self = try .dog(container.decode(Dog.self))
} catch {
do {
// Try to decode as a Turtle, if this fails too, you have a type mismatch
self = try .turtle(container.decode(Turtle.self))
} catch {
// throw type mismatch error
throw DecodingError.typeMismatch(DogOrTurtle.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Dog or Turtle)") )
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .dog(let dog):
try container.encode(dog)
case .turtle(let turtle):
try container.encode(turtle)
}
}
}
使用这种方法,您无需担心数组中 Dog
或 Turtle
的顺序。元素可以以任何顺序和数字出现。
用法:(虽然我故意把狗移到第三个索引处)
let jsonData = """
[
{
"color": "green",
"eats": "lettuce"
},
{
"color": "brown",
"eats": "spinach"
},
{
"name": "Spot",
"breed": "dalmation"
},
{
"color": "yellow",
"eats": "cucumbers"
}
]
""".data(using: .utf8)!
do {
let array = try JSONDecoder().decode([DogOrTurtle].self, from: jsonData)
array.forEach { (dogOrTurtle) in
switch dogOrTurtle {
case .dog(let dog):
print(dog)
case .turtle(let turtle):
print(turtle)
}
}
} catch {
print(error)
}
假设 JSON 看起来像这样:
[
{
"name": "Spot",
"breed": "dalmation"
},
{
"color": "green",
"eats": "lettuce"
},
{
"color": "brown",
"eats": "spinach"
},
{
"color": "yellow",
"eats": "cucumbers"
}
]
您从 API 返回的 JSON 响应中的第一项始终是狗,之后的所有项始终是乌龟。所以项目 0 是狗,项目 1 到 N-1 是乌龟。
如何将其解析为我可以阅读的内容,例如:
struct Result: Codable {
let dog: Dog
let turtles: [Turtle]
}
可能吗?
您可以为您的 Result
结构实现自定义解码器。
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
// Assume the first one is a Dog
self.dog = try container.decode(Dog.self)
// Assume the rest are Turtle
var turtles = [Turtle]()
while !container.isAtEnd {
let turtle = try container.decode(Turtle.self)
turtles.append(turtle)
}
self.turtles = turtles
}
只需少量工作,您就可以支持 Dog
词典位于 Turtle
词典数组中的任何位置。
既然你声明你的结构是 Codable 而不仅仅是 Decodable,你也应该从 Encodable 实现自定义 encode(to:)
但这是留给 reader.
因此您的 Array
包含两种类型的元素。这是 Type1OrType2
问题的一个很好的例子。对于这种情况,您可以考虑使用 enum
和关联类型。对于您的情况,您需要一个 Codable
枚举和 init(from:) throws
& func encode(to:) throws
enum DogOrTurtle: Codable {
case dog(Dog)
case turtle(Turtle)
struct Dog: Codable {
let name: String
let breed: String
}
struct Turtle: Codable {
let color: String
let eats: String
}
}
extension DogOrTurtle {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
// First try to decode as a Dog, if this fails then try another
self = try .dog(container.decode(Dog.self))
} catch {
do {
// Try to decode as a Turtle, if this fails too, you have a type mismatch
self = try .turtle(container.decode(Turtle.self))
} catch {
// throw type mismatch error
throw DecodingError.typeMismatch(DogOrTurtle.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Dog or Turtle)") )
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .dog(let dog):
try container.encode(dog)
case .turtle(let turtle):
try container.encode(turtle)
}
}
}
使用这种方法,您无需担心数组中 Dog
或 Turtle
的顺序。元素可以以任何顺序和数字出现。
用法:(虽然我故意把狗移到第三个索引处)
let jsonData = """
[
{
"color": "green",
"eats": "lettuce"
},
{
"color": "brown",
"eats": "spinach"
},
{
"name": "Spot",
"breed": "dalmation"
},
{
"color": "yellow",
"eats": "cucumbers"
}
]
""".data(using: .utf8)!
do {
let array = try JSONDecoder().decode([DogOrTurtle].self, from: jsonData)
array.forEach { (dogOrTurtle) in
switch dogOrTurtle {
case .dog(let dog):
print(dog)
case .turtle(let turtle):
print(turtle)
}
}
} catch {
print(error)
}