【问题标题】:Decode nested JSON with similar keys using Codable使用 Codable 解码具有相似键的嵌套 JSON
【发布时间】:2018-11-10 06:02:54
【问题描述】:

我正在尝试解码嵌套的 JSON。问题是顶层和嵌套键的名称相似。喜欢:

{
    success: bool
     message: String
     error: {
      message: String
         }
} 

我会从后端收到一条成功消息或一条失败消息。如果成功为真,则不会返回错误键,如果为假,则将错误与消息一起发送。

所以如果成功的话:

{
    success: true
     message: "Success message"
} 

如果失败:

    {
        success: false
         error:{
              message: "Failed message"
        }
    } 

以上将是返回的json。这是我的解码结构:

struct loginResponse : Codable{
    var success: Bool
    var success_message: String
    var error_message: String


enum loginResponseKeys: String, CodingKey{
    case success
    case error
    case success_message = "message" // raw value is not unique
    case error_message = "message"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: loginResponseKeys.self)
    let error = try container.nestedContainer(keyedBy: loginResponseKeys.self, forKey: .error)
    error_message = try error.decode(String.self, forKey: .error_message)
    let message = try container.decode(String.self, forKey:.success_message)
}

没错,它说原始值不是唯一的。但是我该如何克服呢?

【问题讨论】:

  • 为错误创建另一个枚举。例如errorResponseKeys 包含一条消息。当你得到 nestedContainer 使用 errorResponseKeys.self 而不是 loginResponseKeys.self

标签: ios json swift codable


【解决方案1】:

您可以为 ErrorMessage 创建结构

struct LoginResponse: Codable {
    let success: Bool
    let message: String?
    let error: ErrorMessage?
}

struct ErrorMessage: Codable {
    let message: String?
}


extension LoginResponse {
    init(data: Data) throws {
        self = try JSONDecoder().decode(LoginResponse.self, from: data)
    }
}

假设这个 Json:

{
    "success": true,
     "message": "success",
     "error": {
      "message": "Error Message"
         }
} 

【讨论】:

  • 谢谢!我必须把头绕在解码器和编码器上。谢谢
  • 不客气,在可编码的任何 Dictionary 中的 Json {} 转换为 struct ,就像在第一阶段那样想
  • 谢谢。良好的起点。
【解决方案2】:

你只需要创建一个嵌套的ErrorResponse 结构。将messageerror 设为可选,并且根据success 的值仅解码其中一个。

您还应该遵守 Swift 命名约定,类型名称为 UpperCamelCase,变量名称为 lowerCamelCase。

struct LoginResponse : Codable{
    let success: Bool
    var successMessage: String?
    var error: ErrorResponse?

    struct ErrorResponse: Codable {
        let message: String
    }


    enum LoginResponseKeys: String, CodingKey{
        case success, error, successMessage = "message"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: LoginResponseKeys.self)
        success = try container.decode(Bool.self, forKey: .success)
        if success {
            successMessage = try container.decode(String.self, forKey: .successMessage)
        } else {
            error = try container.decode(ErrorResponse.self, forKey: .error)
        }
    }
}

【讨论】:

    【解决方案3】:

    使用KeyedCodable 可能是,请注意您将拥有单个“消息”属性,该属性将根据“成功”值包含成功或失败消息:

    struct LoginResponse: Codable, Keyedable {
        private(set) var success: Bool!
        private(set) var message: String!
    
        mutating func map(map: KeyMap) throws {
            try success <-> map["success"]
            let messageKey = success ? "message" : "error.message"
            try message <-> map[messageKey]
        }
    
        init(from decoder: Decoder) throws {
            try KeyedDecoder(with: decoder).decode(to: &self)
        }
    }
    

    【讨论】:

    • 看看@AbdelahadDarwish 的回答。不需要第三方库,不需要新的令人困惑的运算符,代码更少。仅仅因为你有一个库要推送,并不意味着它是每个 Codable 问题的正确答案。
    • @Ashley Mills 但这是一种不同的方法,我认为展示替代方案很好。在我的解决方案中,您有单个消息属性。当然你可以手动完成,没有任何依赖
    • 说实话 - 你只是想宣传你的图书馆。你有 5/6 的答案是关于它的。
    • 不完全是。我试图回答它会展示如何使用库来简化 Codable 的手动实现。当然你可能不喜欢,你可以使用 Codable 的标准手动实现,这是你的选择。在 swift4 之前,人们使用的是 ObjectMapper 或 JSONModel 等外部库,但其他人使用的是纯字典。在我看来,这个命题真的很容易编写和理解,最重要的是它不会强迫你在代码中的每个模型上都使用它。但您的投票也是对我的回应,我非常尊重并感谢您。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 2021-03-25
    相关资源
    最近更新 更多