【问题标题】:Swift: Constant in Template DefinitionSwift:模板定义中的常量
【发布时间】:2019-10-07 02:44:44
【问题描述】:

我正在与喜欢将 json 主体封装在另一个对象(例如数据)中的后端开发人员合作:

例子:

GET:/用户/当前:

{
  data: {
          firstName: "Evan",
          lastName: "Stoddard"
        }
}

我只想在响应上调用 json decode 以获取我创建的用户结构,但添加的数据对象需要另一个结构。为了解决这个问题,我创建了一个通用模板类:

struct DecodableData<DecodableType:Decodable>:Decodable {

    var data:DecodableType

}

现在我可以获取我的 json 负载,如果我想获取一个用户结构,只需获取我的模板的数据属性:

let user = JSONDecoder().decode(DecodableData<User>.self, from: jsonData).data

这一切都很好而且很花哨,直到有时,关键 data 并不总是 data

我觉得这很可能是相当微不足道的东西,但是有没有办法可以在模板定义中添加一个参数,以便我可以更改枚举编码键,因为该数据键可能会更改?

类似以下内容?

struct DecodableData<DecodableType:Decodable, Key:String>:Decodable {

    enum CodingKeys: String, CodingKey {
        case data = Key
    }

    var data:DecodableType

}

这样我可以传入目标可解码类以及封装该对象的键。

【问题讨论】:

  • 请查看您得到的错误:枚举大小写的原始值必须是文字
  • 你总是在编译时知道密钥是什么,还是只在运行时才知道?
  • @vadian 我知道这个错误,因此我问这个问题。
  • @robmayoff 我会在编译时知道

标签: ios swift decodable jsondecoder


【解决方案1】:

无需编码密钥。相反,您需要一个简单的容器,将 JSON 解析为只有一个键值对的字典,并丢弃该键。

struct Container<T>: Decodable where T: Decodable {
    let value: T

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let dict = try container.decode([String: T].self)

        guard dict.count == 1 else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "expected exactly 1 key value pair, got \(dict.count)")
        }

        value = dict.first!.value
    }
}

如果 JSON 为空或具有多个键值对,则会引发异常。

假设一个简单的结构如

struct Foo: Decodable, Equatable {
    let a: Int
}

不管key如何,你都可以解析它:

let foo1 = try! JSONDecoder().decode(
    Container<Foo>.self,
    from: #"{ "data": { "a": 1 } }"#.data(using: .utf8)!
).value

let foo2 = try! JSONDecoder().decode(
    Container<Foo>.self,
    from: #"{ "doesn't matter at all": { "a": 1 } }"#.data(using: .utf8)!
).value

foo1 == foo2 // true

这也适用于以 null 为值的 JSON 响应,在这种情况下,您需要将其解析为您的类型的可选项:

let foo = try! JSONDecoder().decode(
    Container<Foo?>.self,
    from: #"{ "data": null }"#.data(using: .utf8)!
).value // nil

【讨论】:

  • 如果他的结构包装器获得另一个属性怎么办?
  • @iDevid 这是一个完全不同的问题。如果您有多个属性,则密钥不再无关紧要,在这种情况下,典型的struct 与相关密钥是要走的路。顺便说一句,您是否对我投了反对票以宣传您的答案?
  • 不,因为问题是“我觉得这很可能是相当微不足道的东西,但是有没有办法可以在我的模板定义中添加一个参数,以便我可以将枚举编码键更改为那个数据键可能会改变?”如果包装器包含一个属性,我认为您的解决方案效果很好,相反,如果变得更大(带有错误和状态)它根本不起作用。
【解决方案2】:

试试这样的:

struct GenericCodingKey: CodingKey {
    var stringValue: String

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

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

    var intValue: Int?

    init?(intValue: Int) {
        return nil
    }
}

struct DecodableData<DecodableType: CustomDecodable>: Decodable {

    var data: DecodableType

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: GenericCodingKey.self)
        data = try container.decode(DecodableType.self, forKey: GenericCodingKey(value: DecodableType.dataKey))
    }
}

protocol CustomDecodable: Decodable {
    static var dataKey: String { get }
}

extension CustomDecodable {
    static var dataKey: String {
        return "data" // This is your default
    }
}


struct CustomDataKeyStruct: CustomDecodable {
    static var dataKey: String = "different"
}

struct NormalDataKeyStruct: CustomDecodable {
    //Change Nothing
}

【讨论】:

    猜你喜欢
    • 2012-07-02
    • 2013-08-07
    • 2020-12-17
    • 1970-01-01
    • 2016-03-05
    • 2020-09-11
    • 1970-01-01
    • 2011-05-17
    • 1970-01-01
    相关资源
    最近更新 更多