使用 Codable 将接收到的 Int 转换为 Bool 解码 JSON
Convert received Int to Bool decoding JSON using Codable
我有这样的结构:
struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
enum CodingKeys: String, CodingKey {
case settings // The top level "settings" key
}
// The keys inside of the "settings" object
enum SettingsKeys: String, CodingKey {
case patientID = "patient_id"
case therapistID = "therapist_id"
case isEnabled = "is_therapy_forced"
}
}
extension JSONModelSettings: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("settings")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the settings object as a nested container
let user = try values.nestedContainer(keyedBy: SettingsKeys.self, forKey: .settings)
// Extract each property from the nested container
patientID = try user.decode(String.self, forKey: .patientID)
therapistID = try user.decode(String.self, forKey: .therapistID)
isEnabled = try user.decode(Bool.self, forKey: .isEnabled)
}
}
和 JSON 这种格式(用于在没有额外包装的情况下从设置中提取密钥的结构):
{
"settings": {
"patient_id": "80864898",
"therapist_id": "78920",
"enabled": "1"
}
}
问题是如何将 "isEnabled" 转换为 Bool,(从 API 得到 1 或 0)
当我尝试解析响应时出现错误:
"Expected to decode Bool but found a number instead."
我的建议是:不要打JSON。尽快将其设置为 Swift 值,尽可能不费吹灰之力,然后在那里进行操作。
你可以定义一个私有的内部结构来保存解码后的数据,像这样:
struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
}
extension JSONModelSettings: Decodable {
// This struct stays very close to the JSON model, to the point
// of using snake_case for its properties. Since it's private,
// outside code cannot access it (and no need to either)
private struct JSONSettings: Decodable {
var patient_id: String
var therapist_id: String
var enabled: String
}
private enum CodingKeys: String, CodingKey {
case settings
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let settings = try container.decode(JSONSettings.self, forKey: .settings)
patientID = settings.patient_id
therapistID = settings.therapist_id
isEnabled = settings.enabled == "1" ? true : false
}
}
其他 JSON 映射框架,例如 ObjectMapper 允许您将 转换函数 附加到 encoding/decoding 进程。看起来 Codable
目前没有等价物。
解码为 String
,然后将其转换为 Bool
,只需修改您的代码行:
("0"
是一个 JSON 字符串,不能解码为 Int
。)
struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
enum CodingKeys: String, CodingKey {
case settings // The top level "settings" key
}
// The keys inside of the "settings" object
enum SettingsKeys: String, CodingKey {
case patientID = "patient_id"
case therapistID = "therapist_id"
case isEnabled = "enabled"//### "is_therapy_forced"?
}
}
extension JSONModelSettings: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("settings")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the settings object as a nested container
let user = try values.nestedContainer(keyedBy: SettingsKeys.self, forKey: .settings)
// Extract each property from the nested container
patientID = try user.decode(String.self, forKey: .patientID)
therapistID = try user.decode(String.self, forKey: .therapistID)
//### decode the value for "enabled" as String
let enabledString = try user.decode(String.self, forKey: .isEnabled)
//### You can throw type mismatching error when `enabledString` is neither "0" or "1"
if enabledString != "0" && enabledString != "1" {
throw DecodingError.typeMismatch(Bool.self, DecodingError.Context(codingPath: user.codingPath + [SettingsKeys.isEnabled], debugDescription: "value for \"enabled\" needs to be \"0\" or \"1\""))
}
//### and convert it to Bool
isEnabled = enabledString != "0"
}
}
在那些情况下,我通常喜欢将模型保留为 JSON 数据,因此在您的情况下是 Ints。然后我将计算属性添加到模型以转换为布尔值等
struct Model {
let enabled: Int
var isEnabled: Bool {
return enabled == 1
}
}
现在是 2021 年,我们在 Swift 5 中使用 PropertyWrappers 有更简单的方法来解决这个问题。
@propertyWrapper
struct BoolFromInt: Decodable {
var wrappedValue: Bool // or use `let` to make it immutable
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let intValue = try container.decode(Int.self)
switch intValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected `0` or `1` but received `\(intValue)`")
}
}
}
用法:
struct Settings: Decodable {
@BoolFromInt var isEnabled: Bool
}
属性 包装器
将 String
s、Int
s、Double
s 或 Bool
s 解码为 Bool
、
只需将 @SomeKindOfBool
放在布尔值 属性 之前,例如:
@SomeKindOfBool public var someKey: Bool
演示:
struct MyType: Decodable {
@SomeKindOfBool public var someKey: Bool
}
let jsonData = """
[
{ "someKey": "true" },
{ "someKey": "yes" },
{ "someKey": "1" },
{ "someKey": 1 },
{ "someKey": "false" },
{ "someKey": "no" },
{ "someKey": "0" },
{ "someKey": 0 }
]
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)
for decodedType in decodedJSON {
print(decodedType.someKey)
}
强大的属性Wrapper背后的实现:
@propertyWrapper
struct SomeKindOfBool: Decodable {
var wrappedValue: Bool
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
//Handle String value
if let stringValue = try? container.decode(String.self) {
switch stringValue.lowercased() {
case "false", "no", "0": wrappedValue = false
case "true", "yes", "1": wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expect true/false, yes/no or 0/1 but`\(stringValue)` instead")
}
}
//Handle Int value
else if let intValue = try? container.decode(Int.self) {
switch intValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expect `0` or `1` but found `\(intValue)` instead")
}
}
//Handle Int value
else if let doubleValue = try? container.decode(Double.self) {
switch doubleValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expect `0` or `1` but found `\(doubleValue)` instead")
}
}
else {
wrappedValue = try container.decode(Bool.self)
}
}
}
如果您需要实施可选的,请查看 this answer here
我有这样的结构:
struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
enum CodingKeys: String, CodingKey {
case settings // The top level "settings" key
}
// The keys inside of the "settings" object
enum SettingsKeys: String, CodingKey {
case patientID = "patient_id"
case therapistID = "therapist_id"
case isEnabled = "is_therapy_forced"
}
}
extension JSONModelSettings: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("settings")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the settings object as a nested container
let user = try values.nestedContainer(keyedBy: SettingsKeys.self, forKey: .settings)
// Extract each property from the nested container
patientID = try user.decode(String.self, forKey: .patientID)
therapistID = try user.decode(String.self, forKey: .therapistID)
isEnabled = try user.decode(Bool.self, forKey: .isEnabled)
}
}
和 JSON 这种格式(用于在没有额外包装的情况下从设置中提取密钥的结构):
{
"settings": {
"patient_id": "80864898",
"therapist_id": "78920",
"enabled": "1"
}
}
问题是如何将 "isEnabled" 转换为 Bool,(从 API 得到 1 或 0) 当我尝试解析响应时出现错误: "Expected to decode Bool but found a number instead."
我的建议是:不要打JSON。尽快将其设置为 Swift 值,尽可能不费吹灰之力,然后在那里进行操作。
你可以定义一个私有的内部结构来保存解码后的数据,像这样:
struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
}
extension JSONModelSettings: Decodable {
// This struct stays very close to the JSON model, to the point
// of using snake_case for its properties. Since it's private,
// outside code cannot access it (and no need to either)
private struct JSONSettings: Decodable {
var patient_id: String
var therapist_id: String
var enabled: String
}
private enum CodingKeys: String, CodingKey {
case settings
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let settings = try container.decode(JSONSettings.self, forKey: .settings)
patientID = settings.patient_id
therapistID = settings.therapist_id
isEnabled = settings.enabled == "1" ? true : false
}
}
其他 JSON 映射框架,例如 ObjectMapper 允许您将 转换函数 附加到 encoding/decoding 进程。看起来 Codable
目前没有等价物。
解码为 String
,然后将其转换为 Bool
,只需修改您的代码行:
("0"
是一个 JSON 字符串,不能解码为 Int
。)
struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
enum CodingKeys: String, CodingKey {
case settings // The top level "settings" key
}
// The keys inside of the "settings" object
enum SettingsKeys: String, CodingKey {
case patientID = "patient_id"
case therapistID = "therapist_id"
case isEnabled = "enabled"//### "is_therapy_forced"?
}
}
extension JSONModelSettings: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("settings")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the settings object as a nested container
let user = try values.nestedContainer(keyedBy: SettingsKeys.self, forKey: .settings)
// Extract each property from the nested container
patientID = try user.decode(String.self, forKey: .patientID)
therapistID = try user.decode(String.self, forKey: .therapistID)
//### decode the value for "enabled" as String
let enabledString = try user.decode(String.self, forKey: .isEnabled)
//### You can throw type mismatching error when `enabledString` is neither "0" or "1"
if enabledString != "0" && enabledString != "1" {
throw DecodingError.typeMismatch(Bool.self, DecodingError.Context(codingPath: user.codingPath + [SettingsKeys.isEnabled], debugDescription: "value for \"enabled\" needs to be \"0\" or \"1\""))
}
//### and convert it to Bool
isEnabled = enabledString != "0"
}
}
在那些情况下,我通常喜欢将模型保留为 JSON 数据,因此在您的情况下是 Ints。然后我将计算属性添加到模型以转换为布尔值等
struct Model {
let enabled: Int
var isEnabled: Bool {
return enabled == 1
}
}
现在是 2021 年,我们在 Swift 5 中使用 PropertyWrappers 有更简单的方法来解决这个问题。
@propertyWrapper
struct BoolFromInt: Decodable {
var wrappedValue: Bool // or use `let` to make it immutable
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let intValue = try container.decode(Int.self)
switch intValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected `0` or `1` but received `\(intValue)`")
}
}
}
用法:
struct Settings: Decodable {
@BoolFromInt var isEnabled: Bool
}
属性 包装器
将 String
s、Int
s、Double
s 或 Bool
s 解码为 Bool
、
只需将 @SomeKindOfBool
放在布尔值 属性 之前,例如:
@SomeKindOfBool public var someKey: Bool
演示:
struct MyType: Decodable {
@SomeKindOfBool public var someKey: Bool
}
let jsonData = """
[
{ "someKey": "true" },
{ "someKey": "yes" },
{ "someKey": "1" },
{ "someKey": 1 },
{ "someKey": "false" },
{ "someKey": "no" },
{ "someKey": "0" },
{ "someKey": 0 }
]
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)
for decodedType in decodedJSON {
print(decodedType.someKey)
}
强大的属性Wrapper背后的实现:
@propertyWrapper
struct SomeKindOfBool: Decodable {
var wrappedValue: Bool
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
//Handle String value
if let stringValue = try? container.decode(String.self) {
switch stringValue.lowercased() {
case "false", "no", "0": wrappedValue = false
case "true", "yes", "1": wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expect true/false, yes/no or 0/1 but`\(stringValue)` instead")
}
}
//Handle Int value
else if let intValue = try? container.decode(Int.self) {
switch intValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expect `0` or `1` but found `\(intValue)` instead")
}
}
//Handle Int value
else if let doubleValue = try? container.decode(Double.self) {
switch doubleValue {
case 0: wrappedValue = false
case 1: wrappedValue = true
default: throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expect `0` or `1` but found `\(doubleValue)` instead")
}
}
else {
wrappedValue = try container.decode(Bool.self)
}
}
}
如果您需要实施可选的,请查看 this answer here