これが私のコードです:
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をオプションではなくすると、デフォルト値になりますか?このためのカスタムパーサーを作成できることはわかっていますが、このようなリクエストがたくさんあり、追加の作業が多すぎます。
それと同じくらい簡単です!
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つのオプションがあります:
null
キーに対して_{}
_の代わりにdata
を返すようにサーバーを変更しますinit(from: Decoder)
を実装し、このケースを手動で処理します{}をnullとして扱うことはできないようです。そのため、代わりにAPI応答を「修正」するための簡単なツールを作成しました。
extension String {
func replaceEmptyJsonWithNull() -> String {
return replacingOccurrences(of: "{}", with: "null")
}
}
他の方法は@VitalyGozhenkoによって説明されており、使用する必要がありますが、サーバーAPIを変更したり、完全なカスタムデコーダーを作成したりすることはできません。