【问题标题】:Swift Codable decode empty json as nil or empty objectSwift Codable 将空 json 解码为 nil 或空对象
【发布时间】:2018-08-03 06:05:09
【问题描述】:

这是我的代码:

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 设为非可选并且它只有默认值?我知道我可以为此创建一个自定义解析器,但我有大量这样的请求,而且需要太多额外的工作。

【问题讨论】:

  • 您为什么希望解析器将 { } 视为 nil?这些不是一回事。
  • 由于服务器 API,我无法更改它,因为它会破坏 android 应用程序。另一方面,android 上的改造理解为一个空对象。

标签: ios swift codable decodable


【解决方案1】:

就这么简单!

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)

【讨论】:

  • 是的,但是 LoginUserResponseData 应该失败,因为它不能用空字典解码!
  • 我们这里不是在讨论空值!但 LoginUserResponseData 初始化应该失败的事实。我将在示例中将其删除
【解决方案2】:

这就是您的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
        }
    }
}

【讨论】:

  • 难道没有将 {} 视为 null 的规则吗? :)
  • 不,@Makalele。您可以做的不是将 data 设为可选字符串,而是将 userIdname 设为可选字符串。这将帮助您省略 init(from decoder:) 应该如下所示:struct LoginUserResponseData : Codable { var userId: String? var name: String? }
  • 我知道,但这是简化的示例。实际上,我有 20 多个领域。我正在考虑在调用解析器之前用 data:null 替换字符串 data:{}。
  • 这也会消除合法错误。比如上游API修改了一个字段的定义,对象会返回nil而不是抛出Type Error。
【解决方案3】:

我的建议是将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
}

【讨论】:

  • 这是一种巧妙的方法,但您能更进一步,将数据声明为 UserData 吗?并在 .error 情况下将其设置为 nil ?这似乎比具有假值的 UserData 对象更可取。
【解决方案4】:

这是因为 {} 是一个空对象,但不是 nil。 你有两个选择:

  1. 在服务器上更改以返回 null 而不是 {}data
  2. 实现自定义初始化器init(from: Decoder)并手动处理这种情况

【讨论】:

  • 1.我无法更改服务器。它已经与使用改造的 Android 应用程序一起工作,并且解析它没有问题。 2. 正如我所说,我不想这样做,因为我在大量查询中遇到了同样的情况。一定有一个简单的方法。
【解决方案5】:

似乎不可能将 {} 视为 null,因此我创建了一个简单的工具来“修复”API 响应:

extension String {

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

}

@Vitaly Gozhenko 描述了其他方式并且应该使用,但我不能更改服务器 API 也不想编写完整的自定义解码器,因为这是一种情况。

【讨论】:

  • 这是黑客行为,而不是编程。您无法保证您的服务器不会返回带有空格的大括号,这是完全有效的 JSON。
  • 我知道这就是为什么我应该使用其他人的答案。由于这个非常具体的案例,我只发布了我使用的内容。我只是没有时间编写所有解码器,因为一件事,我无法更改服务器,因为它可能会破坏已经工作的 android 应用程序。
  • 答案提示了处理空的 200 http 代码,例如或空字符串响应以使其通过解码器:if let httpResponse = response { if httpResponse.statusCode == 200 && data.isEmpty{ if let string = "{}".data(using: .utf8) { return Result { try decoder.decode(T.self, from: string) } } }else { print(context) } }else { print(context) }
【解决方案6】:

哇,好吧,这根本行不通。抱歉。

我晚几年才看到这篇文章,但肯定有 每个解决方案的问题。更改 JSON 是可能的 不切实际,使用try? 消除错误有可能 忽略其他可能合法的错误。

这是我通过扩展在项目中使用的建议解决方案 KeyedDecodingContainer: ``` 文件私有扩展 KeyedDecodingContainer { 私有结构 EmptyObject:可解码 {}

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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多