【问题标题】:Error while trying to Parsing API Response with Alamofire尝试使用 Alamofire 解析 API 响应时出错
【发布时间】:2021-10-17 12:02:57
【问题描述】:

API 响应:

{
    "data": {
        "customer_id": "fb5056fe-d7cf-4b5e-3e32-08d9568a3822",
        "source": "self",
        "type": "customer",
        "language": "en",
        "identity_card": {
            "first_name": "abdulrahman",
            "middle_name": "ahmed",
            "last_name": "ali",
            "localized_first_name": "abdulrahman",
            "localized_middle_name": "ahmed",
            "localized_last_name": "ali",
            "identity_card_number_last_digits": "4416",
            "date_of_birth": "1994-05-15T00:00:00",
            "date_of_birth_calendar_type": "gregorian"
        },
        "income": [],
        "identity_verifications": [
            null
        ],
        "is_identity_verification_complete": false,
        "is_address_complete": false,
        "is_income_complete": false,
        "is_usage_complete": false,
        "is_employment_complete": false,
        "customer_status": 3,
        "customer_status_description": "inactive",
        "wallet_status": 0,
        "wallet_status_description": "pending"
    }
}

我的模特:

struct cst_Value : Codable {
    
    let address : cst_Addres?
    let employment : cst_Employment?
    let income : [cst_Income]?
    let usage : cst_Usage?
    let identityVerification : [cst_IdentityVerification]?
    
    let customer_id : String?
    let source : String?
    let type : String?
    let language : String?
    let email : String?
    let phone_number : String?
    let identity_card : cst_IdentityCard?
    let is_identity_verification_complete : Bool?
    let is_address_complete : Bool?
    let is_income_complete : Bool?
    let is_usage_complete : Bool?
    let is_employment_complete : Bool?
    let status : String?
    
    enum CodingKeys: String, CodingKey {
        case customer_id = "customer_id"
        case source = "source"
        case type = "type"
        case language = "language"
        case email = "email"
        case phone_number = "phone_number"
        case identity_card = "identity_card"
        case is_identity_verification_complete = "is_identity_verification_complete"
        case is_address_complete = "is_address_complete"
        case is_income_complete = "is_income_complete"
        case is_usage_complete = "is_usage_complete"
        case is_employment_complete  = "is_employment_complete"
        case status = "status"
        
        case address = "address"
        case employment = "employment"
        case income = "income"
        case usage = "usage"
        case identityVerification = "identity_verifications"
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        customer_id = try values.decodeIfPresent(String.self, forKey: .customer_id)
        source = try values.decodeIfPresent(String.self, forKey: .source)
        type = try values.decodeIfPresent(String.self, forKey: .type)
        language = try values.decodeIfPresent(String.self, forKey: .language)
        email = try values.decodeIfPresent(String.self, forKey: .email)
        phone_number = try values.decodeIfPresent(String.self, forKey: .phone_number)
        identity_card = try values.decodeIfPresent(cst_IdentityCard.self, forKey: .identity_card)
        is_identity_verification_complete = try values.decodeIfPresent(Bool.self, forKey: .is_identity_verification_complete)
        is_address_complete = try values.decodeIfPresent(Bool.self, forKey: .is_address_complete)
        is_income_complete = try values.decodeIfPresent(Bool.self, forKey: .is_income_complete)
        is_usage_complete = try values.decodeIfPresent(Bool.self, forKey: .is_usage_complete)
        is_employment_complete = try values.decodeIfPresent(Bool.self, forKey: .is_employment_complete)
        status = try values.decodeIfPresent(String.self, forKey: .status)
        address = try values.decodeIfPresent(cst_Addres.self, forKey: .address)
        employment = try values.decodeIfPresent(cst_Employment.self, forKey: .employment)
        income = try values.decodeIfPresent([cst_Income].self, forKey: .income)
        usage = try values.decodeIfPresent(cst_Usage.self, forKey: .usage)
        identityVerification = try values.decodeIfPresent([cst_IdentityVerification].self, forKey: .identityVerification)
    }
}

struct cst_IdentityVerification : Codable {

    let identityVerificationMethod : String?
    let identityVerificationStatus : String?


    enum CodingKeys: String, CodingKey {
        case identityVerificationMethod = "identity_verification_method"
                case identityVerificationStatus = "identity_verification_status"
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        identityVerificationMethod = try values.decodeIfPresent(String.self, forKey: .identityVerificationMethod)
        identityVerificationStatus = try values.decodeIfPresent(String.self, forKey: .identityVerificationStatus)
    }


}

错误:

▿ Optional<AFError>
  ▿ some : AFError
    ▿ responseSerializationFailed : 1 element
      ▿ reason : ResponseSerializationFailureReason
        ▿ decodingFailed : 1 element
          ▿ error : DecodingError
            ▿ valueNotFound : 2 elements
              - .0 : Swift.KeyedDecodingContainer<MobilePay.cst_IdentityVerification.CodingKeys>
              ▿ .1 : Context
                ▿ codingPath : 3 elements
                  - 0 : CodingKeys(stringValue: "data", intValue: nil)
                  - 1 : CodingKeys(stringValue: "identity_verifications", intValue: nil)
                  ▿ 2 : _JSONKey(stringValue: "Index 0", intValue: 0)
                    - stringValue : "Index 0"
                    ▿ intValue : Optional<Int>
                      - some : 0
                - debugDescription : "Cannot get keyed decoding container -- found null value instead."
                - underlyingError : nil

【问题讨论】:

    标签: ios swift model alamofire


    【解决方案1】:

    这里有很多问题。主要的一个是您拥有具有三层数据的 json,并且您将其视为一个平面结构。你在这里有两个选择。您可以通过手动解开 init(decoder:) 中的所有嵌套级别并手动处理容器来使其适合您现有的数据结构,但更简单的方法是在您的数据结构中建模 json 并允许 Decodable 为您完成.

    您拥有的第一级是顶级data 字典。您可以创建一个包装结构来解码:

    struct Wrapper: Codable {
       let data: cst_Value
    }
    

    然后你需要定义 cst_Value 结构体(注意 Swift 约定是所有类型都应该以大写字母开头)。这与您之前的类似,但由于我们现在依赖于 Decodable,您不需要定义 CodingKeys 或初始化程序。

    struct cst_Value : Codable {
       
       let address : cst_Addres?
       let employment : cst_Employment?
       let income : [cst_Income]?
       let usage : cst_Usage?
       let identityVerification : [cst_IdentityVerification?]?
       
       let customer_id : String?
       let source : String?
       let type : String?
       let language : String?
       let email : String?
       let phone_number : String?
       let identity_card : cst_IdentityCard?
       let is_identity_verification_complete : Bool?
       let is_address_complete : Bool?
       let is_income_complete : Bool?
       let is_usage_complete : Bool?
       let is_employment_complete : Bool?
       let status : String?
    

    其中许多字段不存在于您的 JSON 中,但我将它们保留在其中,因为它们是可选的并且可能(?)存在于某些未来的 json 中。如果它们将保持未使用状态,请移除它们。

    还要注意定义的变化

       let identity_verifications : [cst_IdentityVerification?]?
    

    由于我删除了 CodingKeys 枚举,我已将属性名称更改为与 json 键相同的名称(但请注意我的最后一段关于此的内容,以及您可能想要恢复它的原因)。

    这里的json结构是一个带有可选元素的非可选数组,所以数组持有的类型需要是可选的。数组本身也需要是可选的,因为您使用的是decodeIfPresent,因此如果 json 中没有它的条目,它需要能够接受 nil。

    最后,json 中未被处理的第三层是identity_card 的条目。为了支持这一点,需要一个适当的类型:

    struct cst_IdentityCard: Codable {
       let first_name: String
       let middle_name: String
       let last_name: String
       let localized_first_name: String
       let localized_middle_name: String
       let localized_last_name: String
       let identity_card_number_last_digits: String
       let date_of_birth: String
       let date_of_birth_calendar_type: String
    }
    

    一旦所有这些组件就位,您就可以解码Wrapper 类型,然后从其data 字段中提取您想要的数据:

    let decoder = JSONDecoder()
    let jsonData = json.data(using: .utf8)!
    do {
       let output = try decoder.decode(Wrapper.self, from: jsonData)
       print(output)
    } catch {
       print(error)
    }
    

    这样做会成功解码,输出为:

    Wrapper(data: __lldb_expr_12.cst_Value(address: nil, employment: nil,
    income: Optional([]), usage: nil, identityVerification: nil, customer_id: 
    Optional("fb5056fe-d7cf-4b5e-3e32-08d9568a3822"), source: 
    Optional("self"), type: Optional("customer"), language: Optional("en"), email: nil, phone_number: nil, identity_card: 
    Optional(__lldb_expr_12.cst_IdentityCard(first_name: "abdulrahman", 
    middle_name: "ahmed", last_name: "ali", localized_first_name: 
    "abdulrahman", localized_middle_name: "ahmed", localized_last_name: "ali", 
    identity_card_number_last_digits: "4416", date_of_birth: "1994-05-
    15T00:00:00", date_of_birth_calendar_type: "gregorian")), 
    is_identity_verification_complete: Optional(false), is_address_complete: 
    Optional(false), is_income_complete: Optional(false), is_usage_complete: 
    Optional(false), is_employment_complete: Optional(false), status: nil))
    
    

    注意这里还有很多 nil 值,因为很多字段没有在 json 中定义。

    如果您愿意,您可以将其处理成扁平的数据结构。

    虽然涉及更多数据类型,但这种方法更易于编码和管理,因为Decodable 为您完成大部分工作,您不必担心CodingKeysinit(decoder:)

    您可能要考虑的另一件事是重新引入 CodingKeys 枚举并使用它来将更多“Swifty”属性名称映射到 json 键。

    【讨论】:

    • 谢谢你,我刚刚改变了模型,因为你提到了我应该做些什么来处理响应 bold self.authorizedController.AddEmployment(params: params).responseDecodable(of: GenericResponse.self) { response in if response.value?.datas != nil{ var cst = CustomerContext.CurrentCustomer if cst == nil { cst = Customer.init(entity: NSEntityDescription.entity(forEntityName: "Customer" , in: DataStore.instance.managedObjectContext)!, insertInto: DataStore.instance.managedObjectContext) } cst!.customer_id = response.value?.datas?.customer_id
    • 我不能说。应用程序中处理数据的逻辑和方式是您需要决定的。
    【解决方案2】:

    错误信息中给出了问题的根源

    • 1 : CodingKeys(stringValue: "identity_verifications", intValue: nil)

    这应该会提示您检查该数据结构和相关联的 JSON。

    属性定义为

    let identityVerification : [cst_IdentityVerification]?
    

    所以,cst_IdentityVerification 元素的可选数组。

    然而 JSON 是

    "identity_verifications": [
                null
            ],
    

    所以是一个带有可选元素的非可选数组。

    这不一样。定义中的数组是可选的,数据中的数组是非可选的,但有一个元素是可选的。

    查看JSON你需要的数据结构

    let identityVerification : [cst_IdentityVerification?]
    

    虽然如果数组本身有可能为零,您将需要

    let identityVerification : [cst_IdentityVerification?]?
    

    【讨论】:

    • 我刚试过你的解决方案,但还是一样:(
    • 刚刚在我的笔记本电脑上看了一下,发现我在手机上阅读时错过了一些更大的问题。我已经发布了另一个答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-14
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-30
    相关资源
    最近更新 更多