簡體   English   中英

如何在 Swift 中使用 Codable 解碼具有不同對象的 JSON 數組?

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

我有一個 JSON,它由一個頂級對象組成,然后是一個由不同 JSON 對象組成的數組。 我想用最少的結構和沒有可選變量來解碼這個 json。 如果我可以實現,我還想設計一個結構,通過只編寫其相關結構來處理所有數組對象。

我會盡量簡化這個例子

JSON 圖像

正如您在圖像中看到的,“Id”、“Token”、“ServicePublicKey”都是不同的 JSON 對象。 我的整個后端都以這種 JSON 架構返回。 我想要實現的是一個結構作為包裝器和結構(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 個元素?

我想要實現的是一個結構作為包裝器和結構(Id、ServicePublicKey、Token 等)。 最后,當有來自 JSON 的新類型時,我只需要編寫相關的結構並在包裝器中添加一些代碼。 我的問題是:如何在沒有任何可選變量的情況下解析這個 JSON?

首先我完全同意你的想法。 在解碼 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
}

服務器公鑰模型

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

日期解碼

我把解碼日期留給你作為作業;-)

更新

此附加部分應回答您的評論

我們可以在沒有響應數組的情況下將 id、serverPublicKey 等變量存儲在 Result 結構中嗎? 我的意思是我們可以只擁有屬性而不是 ResponseArray 嗎? 我認為它需要一種映射,但我無法弄清楚。

是的,我認為我們可以。

我們需要在上面已經描述的結構中再添加一個結構。

這里是

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 結構有一個初始化器,它接收一個 Result 值並嘗試填充它的 3 個屬性

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

如果一切順利,我的意思是如果輸入Result至少包含一個ID 、一個Token和一個ServerPublicKeyAccessibleResponse被初始化,否則 init 失敗並返回 nil`。

測試

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

暫無
暫無

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

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