【问题标题】:Using Decodable protocol with multiples keys使用具有多个键的 Codable 协议
【发布时间】:2017-10-13 06:17:55
【问题描述】:

假设我有以下代码:

import Foundation

let jsonData = """
[
    {"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
    {"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
]
""".data(using: .utf8)!

struct Person: Codable {
    let firstName, lastName: String
    let age: String?

    enum CodingKeys : String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
print(decoded)

除了age 始终为nil 之外,一切正常。这是有道理的。我的问题是如何在第一个示例中设置 Person 的年龄 = realage28,在第二个示例中设置 nil。而不是age 在这两种情况下都是nil,我希望它在第一种情况下是28

有没有办法只使用CodingKeys 而不必添加另一个结构或类来实现这一点?如果不是,我如何使用另一个结构或类以最简单的方式实现我想要的?

【问题讨论】:

  • 你可以在里面写,自己编码。在那里你可以要求嵌套的年龄容器并相应地设置值。
  • @HAS 所以我刚刚在Encode and Decode ManuallyAdditionalInfoKeys 上找到了this。但是我收到一个错误Designated initializer cannot be declared in an extension of 'Person'; did you mean this to be a convenience initializer?Initializer requirement 'init(from:)' can only be satisfied by a 'required' initializer in the definition of non-final class 'Person'
  • 是的,你不能在扩展中这样做,你必须把它写在你声明类型的地方。

标签: json swift swift4 codable


【解决方案1】:

在解码嵌套的 JSON 数据时,我最喜欢的方法是定义一个与 JSON 非常接近的“原始”模型,甚至在需要时使用 snake_case。它有助于将 JSON 数据快速导入 Swift,然后您可以使用 Swift 进行所需的操作:

struct Person: Decodable {
    let firstName, lastName: String
    let age: String?

    // This matches the keys in the JSON so we don't have to write custom CodingKeys    
    private struct RawPerson: Decodable {
        struct RawAge: Decodable {
            let realage: String?
            let fakeage: String?
        }

        let firstname: String
        let lastname: String
        let age: RawAge
    }

    init(from decoder: Decoder) throws {
        let rawPerson  = try RawPerson(from: decoder)
        self.firstName = rawPerson.firstname
        self.lastName  = rawPerson.lastname
        self.age       = rawPerson.age.realage
    }
}

另外,我建议您谨慎使用Codable,因为它同时暗示了EncodableDecodable。看来您只需要Decodable,因此您的模型仅符合该协议。

【讨论】:

    【解决方案2】:

    为了获得更大的灵活性和稳健性,您可以实现Age 枚举以完全支持您的数据模型;)例如:

    enum Age: Decodable {
        case realAge(String)
        case fakeAge(String)
    
        private enum CodingKeys: String, CodingKey {
            case realAge = "realage", fakeAge = "fakeage"
        }
    
        init(from decoder: Decoder) throws {
            let dict = try decoder.container(keyedBy: CodingKeys.self)
            if let age = try dict.decodeIfPresent(String.self, forKey: .realAge) {
                self = .realAge(age)
                return
            }
            if let age = try dict.decodeIfPresent(String.self, forKey: .fakeAge) {
                self = .fakeAge(age)
                return
            }
            let errorContext = DecodingError.Context(
                codingPath: dict.codingPath,
                debugDescription: "Age decoding failed"
            )
            throw DecodingError.keyNotFound(CodingKeys.realAge, errorContext)
        }
    }
    

    然后在您的Person 类型中使用它:

    struct Person: Decodable {
        let firstName, lastName: String
        let age: Age
    
        enum CodingKeys: String, CodingKey {
            case firstName = "firstname"
            case lastName = "lastname"
            case age
        }
    
        var realAge: String? {
            switch age {
            case .realAge(let age): return age
            case .fakeAge: return nil
            }
        }
    }
    

    像以前一样解码:

    let jsonData = """
    [
        {"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
        {"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
    ]
    """.data(using: .utf8)!
    
    let decoded = try! JSONDecoder().decode([Person].self, from: jsonData)
    for person in decoded { print(person) }
    

    打印:

    人(名字:“汤姆”,姓氏:“史密斯”,年龄:Age.realAge(“28”))
    Person(firstName: "Bob", lastName: "Smith", age: Age.fakeAge("31"))


    最后,新的realAge 计算属性提供了您最初所追求的行为(即,非零适用于实际年龄):

    for person in decoded { print(person.firstName, person.realAge) }
    

    汤姆可选(“28”)
    鲍勃无

    【讨论】:

      【解决方案3】:

      有时会欺骗 API 以获取您想要的接口。

      let jsonData = """
      [
          {"firstname": "Tom", "lastname": "Smith", "age": {"realage": "28"}},
          {"firstname": "Bob", "lastname": "Smith", "age": {"fakeage": "31"}}
      ]
      """.data(using: .utf8)!
      
      struct Person: Codable {
          let firstName: String
          let lastName: String
          var age: String? { return _age["realage"] }
      
          enum CodingKeys: String, CodingKey {
              case firstName = "firstname"
              case lastName = "lastname"
              case _age = "age"
          }
      
          private let _age: [String: String]
      }
      
      do {
          let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
          print(decoded)
      
          let encoded = try JSONEncoder().encode(decoded)
          if let encoded = String(data: encoded, encoding: .utf8) { print(encoded) }
      } catch {
          print(error)
      }
      

      这里保留了 API(firstNamelastNameage),并且双向保留了 JSON。

      【讨论】:

        【解决方案4】:

        你可以这样使用:

        struct Person: Decodable {
            let firstName, lastName: String
            var age: Age?
        
            enum CodingKeys: String, CodingKey {
                case firstName = "firstname"
                case lastName = "lastname"
                case age
            }
        }
        
        struct Age: Decodable {
            let realage: String?
        }
        

        你可以这样调用:

        do {
            let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
            print(decoded[0].age?.realage) // Optional("28")
            print(decoded[1].age?.realage) // nil
        } catch {
            print("error")
        }
        

        【讨论】:

          【解决方案5】:

          这里有很多很棒的答案。我有某些理由不想将它变成它自己的数据模型。特别是在我的情况下,它带有很多我不需要的数据,而我需要的这个特定的东西更多地对应于一个人而不是一个年龄模型。

          我相信其他人会发现这篇文章很有用,这太棒了。除此之外,我将发布我决定如何执行此操作的解决方案。

          查看Encoding and Decoding Custom Types Apple Documentation 后,我发现可以构建自定义解码器和编码器来实现这一点(手动编码和解码)。

          struct Coordinate: Codable {
              var latitude: Double
              var longitude: Double
              var elevation: Double
          
              enum CodingKeys: String, CodingKey {
                  case latitude
                  case longitude
                  case additionalInfo
              }
          
              enum AdditionalInfoKeys: String, CodingKey {
                  case elevation
              }
          
              init(from decoder: Decoder) throws {
                  let values = try decoder.container(keyedBy: CodingKeys.self)
                  latitude = try values.decode(Double.self, forKey: .latitude)
                  longitude = try values.decode(Double.self, forKey: .longitude)
          
                  let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
                  elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
              }
          
              func encode(to encoder: Encoder) throws {
                  var container = encoder.container(keyedBy: CodingKeys.self)
                  try container.encode(latitude, forKey: .latitude)
                  try container.encode(longitude, forKey: .longitude)
          
                  var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
                  try additionalInfo.encode(elevation, forKey: .elevation)
              }
          
          }
          

          Apple 未提及的上述代码中包含的一项更改是,您不能像在他们的文档示例中那样使用扩展。所以你必须将它嵌入到结构或类中。

          希望这对某人有所帮助,以及此处的其他惊人答案。

          【讨论】:

          • ...所有这些好的答案只是为了展示这个 Swift 4 编码/解码的东西真的是多么强大/灵活;)
          • @PauloMattos 完全是,只是希望有更多的文档和信息,哈哈。所以希望这个 Stack Overflow 问题将有助于创建它并帮助其他人。
          • @PauloMattos 在this one 上使用我的回答以上的任何想法?
          猜你喜欢
          • 2018-05-23
          • 1970-01-01
          • 2019-08-11
          • 2023-03-06
          • 1970-01-01
          • 2018-11-09
          • 2018-10-26
          • 2018-01-25
          相关资源
          最近更新 更多