繁体   English   中英

有没有更短的方式来声明 CodingKeys?

[英]Is there a shorter way of declaring CodingKeys?

假设您有一个用于 API 响应模型的struct 假设它有 50 个成员。 但是,5-7 个成员是非标准大小写,你可以有AUsernAme_BTmember ,但其余的都是蛇案例credit_scorestatus_code

而不是像这样写所有成员:

struct MyStruct {
  let aUserName: String
  // +50 more...

  private enum CodingKeys: String, CodingKey {
    case aUserName = "AUsernAme"
    // +50 more...
  }
}

有没有办法可以这样写?

struct MyStruct {
  @CodingKey("AUsernAme") let aUserName: String
  let creditScore: Int
  // +50 more ...
}

编辑:我想这在当前的 Swift 版本中是不可能的,但是有谁知道这是否会以某种方式包含在未来的 Swift 版本中?

Sweeper 提供的解决方案可以很好地解决您的问题,但 IMO 可能会对您的问题以及将阅读此代码的下一个开发人员显示出极大的复杂性。

如果我是你,为了简单起见,我会坚持编写所有的 CodingKeys。 如果您担心编写很多行案例,您可以在一行中编写所有不需要自定义键的案例,只需在新行中添加具有异常/非标准大小写的键:

case property1, property2, property3, property4, property5...
case property50 = "_property50"

既然你提到其余的都是蛇盒,不确定你是否知道,但我们有JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase

希望这有助于`tol! :)

在解码之前设置自定义keyDecodingStrategy怎么样?

struct AnyCodingKey: CodingKey, Hashable {
    var stringValue: String

    init(stringValue: String) {
        self.stringValue = stringValue
    }

    var intValue: Int?

    init(intValue: Int) {
        self.intValue = intValue
        self.stringValue = "\(intValue)"
    }
}

let mapping = [
    "AUsernAme": "aUserName",
    // other mappings...
]

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
    let key = codingPath[0].stringValue
    guard let mapped = mapping[key] else { return codingPath.last! }
    return AnyCodingKey(stringValue: mapped)
})

这假设您的 JSON 具有单级平面结构。 你可以把它变成一个扩展:

extension JSONDecoder.KeyDecodingStrategy {
    static func mappingRootKeys(_ dict: [String: String]) -> JSONDecoder.KeyDecodingStrategy {
        return .custom { codingPath in
            let key = codingPath[0].stringValue
            guard let mapped = dict[key] else { return codingPath.last! }
            return AnyCodingKey(stringValue: mapped)
        }
    }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .mappingRootKeys(mapping)

如果您的 JSON 有更多级别,您可以将字典的类型更改为[JSONPath: String] ,其中JSONPath是您可以创建的表示嵌套 JSON 中的键的类型。 然后添加一些将编码路径(只是编码键数组)转换为JSONPath的代码。 这应该不难自己写。

一种简单的方法是将[AnyCodingKey]用作JSONPath ,但也有许多其他方法,我鼓励您尝试并找到您最喜欢的方法。

typealias JSONPath = [AnyCodingKey]

extension AnyCodingKey {
    init(codingKey: CodingKey) {
        if let int = codingKey.intValue {
            self.init(intValue: int)
        } else {
            self.init(stringValue: codingKey.stringValue)
        }
    }
}


extension JSONDecoder.KeyDecodingStrategy {
    static func mappingRootKeys(_ dict: [JSONPath: String]) -> JSONDecoder.KeyDecodingStrategy {
        return .custom { codingPath in
            guard let mapped = dict[codingPath.map(AnyCodingKey.init(codingKey:))] else { return codingPath.last! }
            return AnyCodingKey(stringValue: mapped)
        }
    }
}

let mapping = [
    [AnyCodingKey(stringValue: "AUsernAme")]: "aUserName"
]

不可能为此使用属性包装器。 你的属性包装器@CodingKey("AUsernAme") let aUserName: String将被编译成这样的东西(根据这里):

private var _aUserName: CodingKey<String> = CodingKey("AUsernAme") 
var aUserName: String {
    get { _aUserName.wrappedValue }
    set { _aUserName.wrappedValue = newValue }
}

这有两个主要问题:

  • 假设您不想为MyStruct中的所有 50 多个属性编写init(from:) ,则将合成代码以对其进行解码,并分配给它的_aUserName属性。 您只能控制CodingKey属性包装器的init(from:)初始化程序,并且您无法对其中的MyStruct解码方式做任何事情。 如果MyStruct包含在另一个结构中:

     struct AnotherStruct: Decodable { let myStruct: MyStruct }

    然后,您确实可以通过使用属性包装器对其进行标记来控制用于解码myStruct的编码键。 你可以通过实现属性包装器的init(from:)在解码过程中做任何你想做的事情,这给我们带来了第二个问题:

  • 您传递给CodingKey属性包装器的编码密钥是通过init(_ key: String)形式的初始化程序传递的。 但是您通过初始化程序init(from decoder: Decoder)控制解码,因为这是在解码结构时将调用的内容。 换句话说,您无法将键映射发送到属性包装器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM