Swift Decodable 预设字段来自 JSON

问题描述

我有以下模型:

struct User: Decodable {
  var user: String,var email: String,var selected: Bool = false
}

我的 JSON:

{
   "user":"foo","email":"foo@bar.com"
}

将 JSON 作为数据加载并将其解码为 User 对象时,出现故障。因为它缺少所选的键。但是,我在 User 对象本身中提供了一个认值。有没有一种简单的方法可以在解析用户 json 时不包含“selected”?

例如,由于缺少其中一个键而失败

let obj: User = try! JSONDecoder().decode(User.type,from: data)

(当密钥存在于 JSON 中时,它可以工作,但我想向用户添加一些“本地”数据)(例如,使用该软件的人选择了哪个用户)。

解决方法

只需指定 CodingKeys。不在枚举中的所有键都将被忽略。

struct User: Decodable {
  var user: String
  var email: String
  var selected: Bool = false

  private enum CodingKeys: String,CodingKey { case user,email }
}
,

您可以使用自定义解码器:

struct User: Codable {
    let user: String
    let email: String
    let selected: Bool

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        user = try container.decode(String.self,forKey: .user)
        email = try container.decode(String.self,forKey: .email)

        let decodedSelected = try container.decodeIfPresent(Bool.self,forKey: .selected)
        selected = decodedSelected ?? false // ? Here is the default value
    }
}

此外,您可以使用属性包装器,但它需要更多的解码代码。如果您有兴趣,请查看this article

,

基于文章的答案 (@propertyWrapper):https://www.programmersought.com/article/94997602083/

protocol DefaultValue {
    associatedtype Value: Decodable
    static var defaultValue: Value { get }
}

@propertyWrapper
struct Default<T: DefaultValue> {
    var wrappedValue: T.Value
}

extension Default: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = (try? container.decode(T.Value.self)) ?? T.defaultValue
    }
}

extension KeyedDecodingContainer {
    func decode<T>(
        _ type: Default<T>.Type,forKey key: Key
    ) throws -> Default<T> where T: DefaultValue {
        try decodeIfPresent(type,forKey: key) ?? Default(wrappedValue: T.defaultValue)
    }
}

extension Bool {
    enum False: DefaultValue {
        static let defaultValue = false
    }
    enum True: DefaultValue {
        static let defaultValue = true
    }
}

struct User: Decodable {
    let user: String
    let email: String
    @Default<Bool.True> var selected: Bool // Default Value: true
}

/// Make Network Request
typealias RequestCompletionHandler<T: Decodable> = (_ value: T?,_ error: Error?) -> Void


func callAPI<T: Decodable>(completionHandler: RequestCompletionHandler<T>) {
    let data = """
{
   "user":"foo","email":"foo@bar.com"
}
""".data(using: .utf8)!
    do {
        let value = try JSONDecoder().decode(T.self,from: data)
        completionHandler(value,nil)
    } catch {
        completionHandler(nil,error)
    }
}

callAPI { (model: User?,error) in
    if let finalModel = model {
        print("\(finalModel)")
    } else if let error = error {
        print("Error: \(error)")
    }
}