我如何解码 JSON 可变类型的数据字段?
How can i decode JSON data field that are of changeable types?
我从 API 接收 JSON 数据,但有些字段有时是字符串,有时是整数。此类问题的最佳解决方案是什么?
这是我的解码代码:
public struct Nutriments {
public let energy: String?
public let energyServing: String?
public let energy100g: String?
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
energy = try container.decodeIfPresent(String.self, forKey: .energy)
energy100g = try container.decodeIfPresent(String.self, forKey: .energy100g)
energyServing = try container.decodeIfPresent(String.self, forKey: .energyServing)
}
}
JSON 示例:
"nutriments": {
"energy_100g": 8.97,
"energy_serving": "55",
"energy": "7"
}
其他时间是这样的:
"nutriments": {
"energy_100g": "8.97",
"energy_serving": 55,
"energy": 7
}
首先责怪服务的所有者发送不一致的数据。
要解码这两种类型,您可以在 init
方法中检查类型。
至少 API 似乎发送了所有键,因此您可以将所有结构成员声明为非可选
public struct Nutriments {
public let energy: String
public let energyServing: String
public let energy100g: String
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do { energy = try container.decode(String.self, forKey: .energy) }
} catch DecodingError.typeMismatch { energy = String(try container.decode(Int.self, forKey: .energy)) }
do { energy100g = try container.decode(String.self, forKey: .energy100g) }
} catch DecodingError.typeMismatch { energy100g = String(try container.decode(Int.self, forKey: .energy100g)) }
do { energyServing = try container.decode(String.self, forKey: .energyServing) }
} catch DecodingError.typeMismatch { energyServing = String(try container.decode(Int.self, forKey: .energyServing)) }
}
}
一个解决方案是在 Double
上声明一个自定义包装器,它知道如何从字符串或双精度数中解码自己:
struct DoubleLike: Decodable {
public let value: Double
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
value = try container.decode(Double.self)
} catch DecodingError.typeMismatch {
let valueString = try container.decode(String.self)
if let dbl = Double(valueString) {
value = dbl
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Could not convert \(valueString) to Double"))
}
}
}
}
您可以在您的结构中轻松使用它:
struct Nutriments: Decodable {
public let energy: DoubleLike
public let energyServing: DoubleLike
public let energy100g: DoubleLike
enum CodingKeys: String, CodingKey {
case energy
case energyServing = "energy_serving"
case energy100g = "energy_100g"
}
}
此解决方案的优点是可扩展,缺点是您总是需要提取 .value
属性 才能使用解码后的双精度。
另一种解决方案是编写您自己的 Decoder
实现,但这可能不值得付出努力。
我从 API 接收 JSON 数据,但有些字段有时是字符串,有时是整数。此类问题的最佳解决方案是什么?
这是我的解码代码:
public struct Nutriments {
public let energy: String?
public let energyServing: String?
public let energy100g: String?
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
energy = try container.decodeIfPresent(String.self, forKey: .energy)
energy100g = try container.decodeIfPresent(String.self, forKey: .energy100g)
energyServing = try container.decodeIfPresent(String.self, forKey: .energyServing)
}
}
JSON 示例:
"nutriments": {
"energy_100g": 8.97,
"energy_serving": "55",
"energy": "7"
}
其他时间是这样的:
"nutriments": {
"energy_100g": "8.97",
"energy_serving": 55,
"energy": 7
}
首先责怪服务的所有者发送不一致的数据。
要解码这两种类型,您可以在 init
方法中检查类型。
至少 API 似乎发送了所有键,因此您可以将所有结构成员声明为非可选
public struct Nutriments {
public let energy: String
public let energyServing: String
public let energy100g: String
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do { energy = try container.decode(String.self, forKey: .energy) }
} catch DecodingError.typeMismatch { energy = String(try container.decode(Int.self, forKey: .energy)) }
do { energy100g = try container.decode(String.self, forKey: .energy100g) }
} catch DecodingError.typeMismatch { energy100g = String(try container.decode(Int.self, forKey: .energy100g)) }
do { energyServing = try container.decode(String.self, forKey: .energyServing) }
} catch DecodingError.typeMismatch { energyServing = String(try container.decode(Int.self, forKey: .energyServing)) }
}
}
一个解决方案是在 Double
上声明一个自定义包装器,它知道如何从字符串或双精度数中解码自己:
struct DoubleLike: Decodable {
public let value: Double
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
value = try container.decode(Double.self)
} catch DecodingError.typeMismatch {
let valueString = try container.decode(String.self)
if let dbl = Double(valueString) {
value = dbl
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Could not convert \(valueString) to Double"))
}
}
}
}
您可以在您的结构中轻松使用它:
struct Nutriments: Decodable {
public let energy: DoubleLike
public let energyServing: DoubleLike
public let energy100g: DoubleLike
enum CodingKeys: String, CodingKey {
case energy
case energyServing = "energy_serving"
case energy100g = "energy_100g"
}
}
此解决方案的优点是可扩展,缺点是您总是需要提取 .value
属性 才能使用解码后的双精度。
另一种解决方案是编写您自己的 Decoder
实现,但这可能不值得付出努力。