簡體   English   中英

Swift Codable 將空 json 解碼為 nil 或空對象

[英]Swift Codable decode empty json as nil or empty object

這是我的代碼:

class LoginUserResponse : Codable {
    var result: String = ""
    var data: LoginUserResponseData?
    var mess: [String] = []
}

public class LoginUserResponseData : Codable {
    var userId = "0"
    var name = ""
}

現在,調用服務器 API 我正在解析這樣的響應(使用 Stuff 庫來簡化解析):

do {
    let loginUserResponse = try LoginUserResponse(json: string)
} catch let error {
    print(error)
}

當我輸入正確的密碼時,我會得到這樣的答案:

{"result":"success","data":{"userId":"10","name":"Foo"},"mess":["You're logged in"]}

這很好,解析器工作正常。

提供錯誤密碼時會給出以下答案:

{"result":"error","data":{},"mess":["Wrong password"]}

在這種情況下,解析器失敗。 它應該將 data 設置為 nil,但相反,它會嘗試將其解碼為 LoginUserResponseData 對象。

我在使用改造的 Android 上使用相同的方法,它工作正常。 我寧願不想將所有字段都設為可選。

有沒有辦法讓解析器將空 json {} 視為 nil? 或者將 LoginUserResponseData 設置為非可選的,它只有默認值? 我知道我可以為此創建一個自定義解析器,但是我有大量這樣的請求,並且需要太多額外的工作。

就這么簡單!

class LoginUserResponse : Codable {
    var result: String = ""
    var data: LoginUserResponseData?
    var mess: [String] = []

    private enum CodingKeys: String, CodingKey {
        case result, data, mess
    }

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        result = try values.decode(String.self, forKey: .result)
        mess = try values.decode([String].self, forKey: .mess)
        data = try? values.decode(LoginUserResponseData.self, forKey: .data)
    }
}

public class LoginUserResponseData : Codable {
    var userId = "0"
    var name = ""
}

let str = "{\"result\":\"success\",\"data\":{\"userId\":\"10\",\"name\":\"Foo\"},\"mess\":[\"You're logged in\"]}"
let str2 = "{\"result\":\"error\",\"data\":{},\"mess\":[\"Wrong password\"]}"

let decoder = JSONDecoder()
let result = try? decoder.decode(LoginUserResponse.self, from: str.data(using: .utf8)!)
let result2 = try? decoder.decode(LoginUserResponse.self, from: str2.data(using: .utf8)!)
dump(result)
dump(result2)

我的建議是將result解碼為enum並在成功時初始化data

struct LoginUserResponse : Decodable {

    enum Status : String, Decodable { case success, error }
    private enum CodingKeys: String, CodingKey { case result, data, mess }

    let result : Status
    let data : UserData?
    let mess : [String]

    init(from decoder: Decoder) throws
    {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        result = try values.decode(Status.self, forKey: .result)
        mess = try values.decode([String].self, forKey: .mess)
        switch result {
            case .success: data = try values.decode(UserData.self, forKey: .data)
            case .error: data = nil
        }
    }
}

public struct UserData : Decodable {
    let userId : String
    let name : String
}

這就是您的init(from: Decoder)實現應該是什么樣子。

注意:您應該考慮將LoginUserResponse從類更改為結構,因為它所做的只是存儲值。

struct LoginUserResponse: Codable {
    var result: String
    var data: LoginUserResponseData?
    var mess: [String]

    init(from decoder: Decoder) throws
    {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        result = try values.decode(String.self, forKey: .result)
        mess = try values.decode([String].self, forKey: .mess)
        if let d = try? values.decode(LoginUserResponseData.self, forKey: .data) {
            data = d
        }
    }
}

這是因為{}是一個空對象,但不是 nil。 您有 2 個選項:

  1. 在服務器上更改以返回null而不是{}作為data
  2. 實現自定義初始化程序init(from: Decoder)並手動處理這種情況

似乎不可能將 {} 視為 null,因此我創建了一個簡單的工具來“修復”API 響應:

extension String {

    func replaceEmptyJsonWithNull() -> String {
        return replacingOccurrences(of: "{}", with: "null")
    }

}

@Vitaly Gozhenko 描述了其他方法並且應該使用,但我不能更改服務器 API,也不想編寫完整的自定義解碼器,因為這是一種情況。

哇,好吧,這根本不起作用。 對不起。

幾年后我才看到這篇文章,但每個解決方案都存在某些問題。 更改 JSON 可能不切實際, try? 有可能忽略其他潛在的合法錯誤。

這是我通過擴展KeyedDecodingContainer在項目中使用的建議解決方案:``` fileprivate extension KeyedDecodingContainer { private struct EmptyObject: Decodable {}

 func decodePossibleEmptyObject<T: Decodable>(_ key: K) throws -> T? { if let _ = try? decode(EmptyObject.self, forKey: key) { return nil } return try self.decode(T.self, forKey: key) } } ```

創建一個EmptyObject表示允許try? 只有當對象實際上是一個空對象時才會成功。 否則,解碼器將繼續按請求解碼對象,錯誤會落在該方法中。

最大的缺點是這需要自定義init(from: Coder)

我第一次遇到這個問題時,通常后端會發送 nil 但我收到的是空數據。 只需將用戶數據中的數據設為可選,它就會開箱即用。

在需要時展開看起來很乏味,但如果您有 API 層,以及您將從 API 對象構建的業務模型層以及您需要的確切數據,那就完全沒問題了。

struct LoginUserResponse : Codable {
    let result: String
    let data: LoginUserResponseData?
    let mess: [String] = []
}

struct LoginUserResponseData : Codable {
    let userId: String?
    let name: String?
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM