web-dev-qa-db-ja.com

Swift Codableは空のjsonをnilまたは空のオブジェクトとしてデコードします

これが私のコードです:

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"]}

この状況では、パーサーは失敗しています。データをnilに設定する必要がありますが、代わりに、LoginUserResponseDataオブジェクトにデコードしようとします。

Androidレトロフィットを使用して同じアプローチを使用していますが、正常に機能します。すべてのフィールドをオプションにしたくありません。

パーサーに空のjson {}をnilとして扱わせる方法はありますか?または、LoginUserResponseDataをオプションではなくすると、デフォルト値になりますか?このためのカスタムパーサーを作成できることはわかっていますが、このようなリクエストがたくさんあり、追加の作業が多すぎます。

4
Makalele

それと同じくらい簡単です!

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)
5
Arsonik

resultenumとしてデコードし、成功したら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
}
3
vadian

これは、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
        }
    }
}
1
Sam

これは、_{}_が空のオブジェクトであるが、nilではないためです。 2つのオプションがあります:

  1. nullキーに対して_{}_の代わりにdataを返すようにサーバーを変更します
  2. カスタム初期化子init(from: Decoder)を実装し、このケースを手動で処理します
1

{}をnullとして扱うことはできないようです。そのため、代わりにAPI応答を「修正」するための簡単なツールを作成しました。

extension String {

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

}

他の方法は@VitalyGozhenkoによって説明されており、使用する必要がありますが、サーバーAPIを変更したり、完全なカスタムデコーダーを作成したりすることはできません。

0
Makalele