【问题标题】:Swift custom decodable initializer without CodingKeys没有 CodingKeys 的 Swift 自定义可解码初始化器
【发布时间】:2020-07-28 18:21:36
【问题描述】:

假设我有以下可解码结构作为示例,说明我正在尝试做什么:

struct Object: Decodable {
    var id: String
    var name: String
}

还有这个 JSON:

[
    {
        "id": "a",
        "name": "test"
    },
    {
        "id": "b",
        "name": null
    }
]

请注意,名称属性有时可以是null。这通常可以正常工作,因为 json 键与结构属性名称匹配,所以我不需要 CodingKey 枚举,但 name 属性有时可以为空。但是,我不想将name 设为可选,而是替换为默认字符串,因此我需要一个自定义初始化程序:

struct Object: Decodable {
    var id: String
    var name: String

    init(from decoder: Decoder) throws {
        ...
        self.name = <value from decoder> ?? "default name"
        ...
    }
}

但这需要一个CodingKey 对象。我正在使用默认键。我现在还需要CodingKey 枚举吗?即使我所有的钥匙都匹配?或者有没有办法让自定义 Decodable 初始化器只使用键?

我可以使用某种默认容器吗?

let container = try decoder.container(keyedBy: <defaultContainer???>)

我尝试使用这两种变体,但都不起作用:

让容器 = 尝试解码器.singleValueContainer()

let container = try decoder.unkeyedContainer()

我怎样才能有一个自定义的 Decodable 初始化器但也使用默认键?

【问题讨论】:

  • 你不能只使用为你制作的编译器Object.CodingKeys吗?
  • 仅当Codable 被使用时,而不仅仅是其组成部分之一。
  • @Alexander-ReinstateMonica 除非我遗漏了什么,否则它似乎不可用。尝试获取 self.CodingKeysObject.CodingKeys 给出“类型没有成员 'CodingKeys'”
  • @Jessy,你是对的。只要我符合 Codable,我就可以使用 Object.CodingKeys.self,或者只是 CodingKeys.self 来创建容器。我想我必须遵守 Codable 。知道这是为什么吗?您应该使用您的信息创建一个答案。

标签: swift decodable


【解决方案1】:

正如对您问题的评论所说,编译器会为您生成一个 CodingKeys 对象。当您的枚举或类上的键名与您从源接收的 JSON 数据不匹配时,您可以实现自定义 enum

你可以这样实现你的对象:

struct TestObject: Codable {
    var id: String
    var name: String

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let id = try container.decode(String.self, forKey: .id)
        let nameOrNil = try? container.decode(String.self, forKey: .name)

        self.id = id
        self.name = nameOrNil ?? "Default value"
    }
}

containerdecode(_:forKey:) 方法可能会抛出错误,但如果您执行实现以释放错误并使用 try? 返回可选值,您可以应用 nil 合并运算符来分配您的默认值该名称不包含在 JSON 中。

在这里证明:

let json = """
[
    {
        "id": "a",
        "name": "test"
    },
    {
        "id": "b",
        "name": null
    }
]
""".data(using: .utf8)!
let decodedArray = try JSONDecoder().decode([TestObject].self, from: json)
print(decodedArray)

参考文献:

【讨论】:

    【解决方案2】:

    CodingKeys 的自动生成真的很奇怪。它的范围和可用性会根据您拥有的成员而变化。

    假设您只有一个Decodable。这些编译:

    struct Decodable: Swift.Decodable {
      static var codingKeysType: CodingKeys.Type { CodingKeys.self }
    }
    
    struct Decodable: Swift.Decodable {
      static func `init`(from decoder: Decoder) throws -> Self {
        _ = CodingKeys.self
        return self.init()
      }
    }
    

    ...如果您添加private,您可以将它们放在一起。

    struct Decodable: Swift.Decodable {
      private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
    
      static func `init`(from decoder: Decoder) throws -> Self {
        _ = CodingKeys.self
        return self.init()
      }
    }
    

    ...但是将 func 设为初始化器,同样,无需编译。

    struct Decodable: Swift.Decodable {
      private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
    
      init(from decoder: Decoder) throws {
        _ = CodingKeys.self
      }
    }
    

    您可以将其更改为完全Codable,而不仅仅是Decodable...

    struct Decodable: Codable {
      private static var codingKeysType: CodingKeys.Type { CodingKeys.self }
    
      init(from decoder: Decoder) throws {
        _ = CodingKeys.self
      }
    }
    

    但是您不能在类型范围内使用CodingKeys,因此该属性将无法编译。

    考虑到您可能不需要这样的属性,只需使用Codable,向Apple 提交一个引用此答案的错误,希望我们都可以在他们修复它时切换到Decodable。 ?

    【讨论】:

    • func `init` 我什至不知道这是可能的
    【解决方案3】:

    问题是 CodingKeys 只有在没有完全手动符合相关协议的情况下才会为您自动生成。 (这对于 Objective-C 开发人员来说非常熟悉,如果您手动实现所有相关的访问器方法,则不会自动合成属性的支持 ivar。)

    因此,在以下情况下,CodingKeys 不会自动为您创建:

    1. 你只采用Decodable并实现了你自己的init(from:)

    2. 你只采用Encodable并实现了你自己的encode(to:);或

    3. 您同时采用EncodableDecodable(或仅采用Codable)并实现了您自己的init(from:)encode(to:)

    所以您的情况属于上述第一种情况。

    有人建议您可以通过采用 Codable 来解决您的难题,即使您只实现了 init(from:) 并且可能不打算使用 Encodable 行为。实际上,这依赖于您不打算真正使用的协议的副作用。

    这并不重要,采用Codable 是可行的,但继续定义CodingKeys 可能被认为“更正确”,而不是依赖未实现的Encodable 端-效果:

    struct Object: Decodable {
        var id: String
        var name: String
    
        enum CodingKeys: String, CodingKey {
            case id, name
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            id = try container.decode(String.self, forKey: .id)
            name = (try? container.decode(String.self, forKey: .name)) ?? "Default Value"
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用属性包装器来模拟该行为,但这不是完美的解决方案,而且有点 hacky。这个问题已经在 Swift 论坛上讨论过很多次了。

      使用属性包装器:

      struct Object: Decodable {
          var id: String
          @DecodableDefault var name: String
      }
      

      属性包装代码:

      public protocol DecodableDefaultValue: Decodable {
          static var defaultDecodableValue: Self { get }
      }
      
      @propertyWrapper
      public struct DecodableDefault<T: Decodable>: Decodable {
          public var wrappedValue: T
      
          public init(from decoder: Decoder) throws {
              let container = try decoder.singleValueContainer()
              wrappedValue = try container.decode(T.self)
          }
      
          public init(wrappedValue: T) {
              self.wrappedValue = wrappedValue
          }
      }
      
      extension DecodableDefault: Encodable where T: Encodable {
          public func encode(to encoder: Encoder) throws {
              var container = encoder.singleValueContainer()
              try container.encode(wrappedValue)
          }
      }
      
      extension DecodableDefault: Equatable where T: Equatable { }
      extension DecodableDefault: Hashable where T: Hashable { }
      
      public extension KeyedDecodingContainer {
          func decode<T: DecodableDefaultValue>(_: DecodableDefault<T>.Type, forKey key: Key) throws -> DecodableDefault<T> {
              guard let value = try decodeIfPresent(DecodableDefault<T>.self, forKey: key) else {
                  return DecodableDefault(wrappedValue: T.defaultDecodableValue)
              }
      
              return value
          }
      }
      
      extension Array: DecodableDefaultValue where Element: Decodable {
          public static var defaultDecodableValue: [Element] {
              return []
          }
      }
      
      extension Dictionary: DecodableDefaultValue where Key: Decodable, Value: Decodable {
          public static var defaultDecodableValue: [Key: Value] {
              return [:]
          }
      }
      
      extension String: DecodableDefaultValue {
          public static let defaultDecodableValue: String = ""
      }
      
      extension Int: DecodableDefaultValue {
          public static let defaultDecodableValue: Int = 0
      }
      

      列举几个问题:

      • 您不能选择默认值(可以以不同的方式完成,但更复杂)
      • 如果你想使用let,你需要一个单独的不可变包装器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-02
        • 2015-03-04
        • 2020-12-07
        • 2020-06-26
        • 2020-03-05
        • 2018-06-25
        • 2023-01-29
        相关资源
        最近更新 更多