如何使用 Swift 中的 Codable 解码具有不同对象的 JSON 数组?

How to decode JSON Array with different objects with Codable in Swift?

我有一个 JSON,它由一个顶级对象和一个由不同 JSON 对象组成的数组组成。我想用最少的结构和没有可选变量来解码这个 json 。如果我能实现,我也想设计一个结构,通过只写它的相关结构来处理所有的数组对象。

我会尽量简化示例

如图所示,"Id"、"Token"、"ServicePublicKey" 都是不同的 JSON 对象。在这个 JSON 的架构中,我的整个后端 returns。 我想要实现的 是一个结构作为(Id、ServicePublicKey、Token 等)的包装器和结构。最后,当 JSON 有一个新类型出现时,我只需要编写相关的结构并在包装器中添加一些代码。

我的问题是: 我如何在没有任何可选变量的情况下解析这个 JSON?

我如何尝试解析它:

struct Initialization: Decodable {
var error: BunqError? //TODO: Change this type to a right one
var id: Int?
var publicKey: String?
var token: Token?

enum CodingKeys: String, CodingKey {
    case error = "Error"
    case data = "Response"
    case Id = "Id"
    case id = "id"
    case ServerPublicKey = "ServerPublicKey"
    case Token = "Token"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    error = nil
    if let errorArray = try container.decodeIfPresent([BunqError].self, forKey: .error) {
        if !errorArray.isEmpty {
            error = errorArray[0]
        }
    }
    if let unwrappedResponse = try container.decodeIfPresent([Response<Id>].self, forKey: .data) {
        print(unwrappedResponse)
    }
}
}
struct Response<T: Decodable>: Decodable {
let responseModel: T?

enum CodingKeys: String, CodingKey {
    case Id = "Id"
    case ServerPublicKey = "ServerPublicKey"
    case Token = "Token"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    switch "\(T.self)"
    {
    case CodingKeys.Id.rawValue:
        self.responseModel = try container.decode(T.self, forKey: .Id)
        break;
    case CodingKeys.ServerPublicKey.rawValue:
        self.responseModel = try container.decode(T.self, forKey: .ServerPublicKey)
        break;
    default:
        self.responseModel = nil
        break;
    }
}
}

struct Id: Decodable {
let id: Int

enum CodingKeys: String, CodingKey {
    case id = "id"
}
}

struct ServerPublicKey: Decodable {
let server_public_key: String
}
struct Token: Decodable {
let created: String
let updated: String
let id: Int
let token: String
}

Json 示例:

    {
  "Response" : [
    {
      "Id" : {
        "id" : 123456
      }
    },
    {
      "Token" : {
        "token" : "myToken",
        "updated" : "2020-01-11 13:55:43.397764",
        "created" : "2020-01-11 13:55:43.397764",
        "id" : 123456
      }
    },
    {
      "ServerPublicKey" : {
        "server_public_key" : "some key"
      }
    }
  ]
}

问题是:在 Swift 中使用 Codable 解码时如何获取 JSON 数组的第 n 个元素?

What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..). At the end when there is a new type coming from JSON, I need to write only relevant struct and add some code inside wrapper. My Question is that: How can I parse this JSON without any optional variable?

首先我完全同意你的想法。 在解码 JSON 时,我们应该始终瞄准

  • 无可选项(只要后端保证)
  • 易于扩展

开始吧

所以鉴于此 JSON

let data = """
    {
        "Response": [
            {
                "Id": {
                    "id": 123456
                }
            },
            {
                "Token": {
                    "token": "myToken",
                    "updated": "2020-01-11 13:55:43.397764",
                    "created": "2020-01-11 13:55:43.397764",
                    "id": 123456
                }
            },
            {
                "ServerPublicKey": {
                    "server_public_key": "some key"
                }
            }
        ]
    }
""".data(using: .utf8)!

身份证型号

struct ID: Decodable {
    let id: Int
}

代币模型

struct Token: Decodable {
    let token: String
    let updated: String
    let created: String
    let id: Int
}

ServerPublicKey 模型

struct ServerPublicKey: Decodable {
    let serverPublicKey: String
    enum CodingKeys: String, CodingKey {
        case serverPublicKey = "server_public_key"
    }
}

结果模型

struct Result: Decodable {

    let response: [Response]

    enum CodingKeys: String, CodingKey {
        case response = "Response"
    }

    enum Response: Decodable {

        enum DecodingError: Error {
            case wrongJSON
        }

        case id(ID)
        case token(Token)
        case serverPublicKey(ServerPublicKey)

        enum CodingKeys: String, CodingKey {
            case id = "Id"
            case token = "Token"
            case serverPublicKey = "ServerPublicKey"
        }

        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            switch container.allKeys.first {
            case .id:
                let value = try container.decode(ID.self, forKey: .id)
                self = .id(value)
            case .token:
                let value = try container.decode(Token.self, forKey: .token)
                self = .token(value)
            case .serverPublicKey:
                let value = try container.decode(ServerPublicKey.self, forKey: .serverPublicKey)
                self = .serverPublicKey(value)
            case .none:
                throw DecodingError.wrongJSON
            }
        }
    }
}

让我们解码!

我们终于可以破译你的 JSON

do {
    let result = try JSONDecoder().decode(Result.self, from: data)
    print(result)
} catch {
    print(error)
}

输出

这是输出

Result(response: [
    Result.Response.id(
        Result.Response.ID(
            id: 123456
        )
   ),
   Result.Response.token(
        Result.Response.Token(
            token: "myToken",
            updated: "2020-01-11 13:55:43.397764",
            created: "2020-01-11 13:55:43.397764",
            id: 123456)
    ),
    Result.Response.serverPublicKey(
        Result.Response.ServerPublicKey(
            serverPublicKey: "some key"
        )
    )
])

日期解码

我把日期解码留给你作为作业;-)

更新

这个附加部分应该回答您的评论

Can we store variables like id, serverPublicKey inside Result struct without Response array. I mean instead of ResponseArray can we just have properties? I think It need a kind of mapping but I can't figure out.

是的,我想我们可以。

我们需要在上面已经描述的结构上再添加一个结构。

在这里

struct AccessibleResult {

    let id: ID
    let token: Token
    let serverPublicKey: ServerPublicKey

    init?(result: Result) {

        typealias ComponentsType = (id: ID?, token: Token?, serverPublicKey: ServerPublicKey?)

        let components = result.response.reduce(ComponentsType(nil, nil, nil)) { (res, response) in
            var res = res
            switch response {
            case .id(let id): res.id = id
            case .token(let token): res.token = token
            case .serverPublicKey(let serverPublicKey): res.serverPublicKey = serverPublicKey
            }
            return res
        }

        guard
            let id = components.id,
            let token = components.token,
            let serverPublicKey = components.serverPublicKey
        else { return nil }

        self.id = id
        self.token = token
        self.serverPublicKey = serverPublicKey
    }
}

这个 AccessibleResult 结构有一个初始化程序,它接收一个结果值并尝试填充它的 3 个属性

let id: ID
let token: Token
let serverPublicKey: ServerPublicKey

如果一切顺利,我的意思是如果输入 Result 至少包含一个 ID、一个 Token 和一个 ServerPublicKey,那么 AccessibleResponse被初始化,否则初始化失败并返回 nil`。

测试

if
    let result = try? JSONDecoder().decode(Result.self, from: data),
    let accessibleResult = AccessibleResult(result: result) {
        print(accessibleResult)
}