【问题标题】:How to decode a property with type of JSON dictionary in Swift?如何在 Swift 中解码 JSON 字典类型的属性?
【发布时间】:2021-10-30 18:16:09
【问题描述】:

我目前在测试中遇到以下错误:

如果我正确理解了问题,那么我需要将我的字典转换为解码器。

目前我有下一个代码:

static var validConfirmAuthorizationData: [String: Any] { return json("valid_confirm_authorization_data") }

结构如下:

{
    "data": {
        "id": "1",
        "success": true
    }
}

以及我与可解码器一起使用的响应类本身:

public struct SEConfirmAuthorizationResponse: Decodable {
    public let id: String
    public let success: Bool

    enum CodingKeys: String, CodingKey {
        case data
    }

    enum DataCodingKeys: String, CodingKey {
        case id
        case success
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data)
        id = try dataContainer.decode(String.self, forKey: .id)
        success = try dataContainer.decode(Bool.self, forKey: .success)
    }
}

【问题讨论】:

  • @JoakimDanielson 抱歉,但我在文章中没有看到任何关于 JSONDecoder 的信息。据我了解,我应该这样写:让decoder = JSONDecoder() try decoder.decode(???.self, from: fixture) 但不清楚用什么代替???。 self 因为我有 JSON,它只是一个字符串而不是某个模型的类
  • 你有字典吗?为SEConfirmAuthorizationResponseinit(dictionary: [String: Any])添加方法?
  • @Larme 不,我只有public init(from decoder: Decoder) throws { 我扔掉了问题中的示例代码
  • 我会这样做:extension SEConfirmAuthorizationResponse { init?(dict: [String: Any] { guard let id = dict["id"] as? String, let success: dict["success"] as? Bool else { return nil }; self.id = id; self.success = success} }let response = SEConfirmAuthorizationResponse(dict: fixture)

标签: ios json swift codable


【解决方案1】:

你有几个选择,你可以用一个名为 data 的变量创建一个可编码的结构。数据将具有 SEConfirmAuthorizationResponse 类型。然后你会解码成新的结构。

你可以解码成这样的字典:

let decoded = JsonDecoder().decode([String: SEConfirmAuthorizationResponse].self, from: someData)

let response = decoded[“data”]

或者你可以编写一个我通常尽量避免的自定义解码器。

【讨论】:

  • 我不能这样写,因为我得到下一个错误:“无法将类型'[String:Any]'的值转换为预期的参数类型'Data'”
【解决方案2】:
public struct AuthorizationData: Codable {
    public let id: String
    public let success: Bool
    
    init(id: String, success: Bool) {
        self.id = id
        self.success = success
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case success
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        success = try container.decode(Bool.self, forKey: .success)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(success, forKey: .success)
    }
}

public struct SEConfirmAuthorizationResponse: Codable {
    public let data: AuthorizationData
    
    enum CodingKeys: String, CodingKey {
        case data
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        data = try container.decode(AuthorizationData.self, forKey: .data)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(data, forKey: .data)
    }
    
    init(_ data: AuthorizationData) {
        self.data = data
    }
}

func authData() -> Data? {
    let authorizationData = AuthorizationData(id: "1", success: true)
    let response = SEConfirmAuthorizationResponse(authorizationData)
    
    guard let prettyJsonData = try? JSONEncoder().encode(response) else {
        return nil
    }
    if let jsonString = String(data: prettyJsonData, encoding: .utf8) {
        print("jsonString", jsonString)
    }
    return prettyJsonData
}

func authResponse() {
    if let data = authData() {
        guard let response = try? JSONDecoder().decode(SEConfirmAuthorizationResponse.self, from: data) else {
            return
        }
        print("response", response)
    }
}

【讨论】:

  • 请添加更多详细信息以扩展您的答案,例如工作代码或文档引用。
【解决方案3】:

原来是这样解决问题的。

首先,我创建了SpecDecodableModel

public struct SpecDecodableModel<T: Decodable> {
    static func create(from fixture: [String: Any]) -> T {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter]
        let fixtureData = Data(fixture.jsonString!.utf8)
        return try! decoder.decode(T.self, from: fixtureData)
    }
}

之后,我可以将我的 JSON 结构转换为所需的类型。

现在回到原来的问题,我可以如下解决:

let fixture = DataFixtures.validConfirmAuthorizationData
let response = SpecDecodableModel<SEConfirmAuthorizationResponse>.create(from: fixture)

更新

public extension Dictionary {

    var jsonString: String? {
        if let data = try? JSONSerialization.data(withJSONObject: self, options: []),
            let string = String(data: data, encoding: String.Encoding.utf8) {
            return string
        }
        return nil
    }
}

【讨论】:

  • 这不是一个完整的答案,Dictionary 没有属性jsonString。这一定是您自己添加的或来自某个 3rd 方库的内容,所以请解释一下它是什么。
  • 我遇到了一个问题,即我无法将我的灯具用作 SEConfirmAuthorizationResponse 的参数,因为它实现了可解码。所以我写了一个 SpecDecodableModel,它使用 JSONDecoder,允许你从我的可解码类的 JSON 字符串(我的夹具)中解码。
  • 这并不能澄清您的答案,无论如何您都不应该尝试在评论中这样做。请通过解释jsonString 是什么来更新您的答案。
  • @JoakimDanielson 已更新。
猜你喜欢
  • 2017-11-20
  • 1970-01-01
  • 2020-10-19
  • 2018-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-09
相关资源
最近更新 更多