简体   繁体   English

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

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

I have a JSON which consist of a top object then an array which consist of different JSON Objects.我有一个 JSON,它由一个顶级对象组成,然后是一个由不同 JSON 对象组成的数组。 I want to decode this json with minimal struct and without optional variables.我想用最少的结构和没有可选变量来解码这个 json。 If I can achieve, I also want to design a struct which handles all of the array objects via writing only Its relevant struct.如果我可以实现,我还想设计一个结构,通过只编写其相关结构来处理所有数组对象。

I'll try to simplify the example我会尽量简化这个例子

JSON 图像

As You can see in the image both "Id", "Token", "ServicePublicKey" are different JSON objects.正如您在图像中看到的,“Id”、“Token”、“ServicePublicKey”都是不同的 JSON 对象。 Whole of my backend returns in this architecture of JSON.我的整个后端都以这种 JSON 架构返回。 What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..).我想要实现的是一个结构作为包装器和结构(Id、ServicePublicKey、Token 等)。 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.最后,当有来自 JSON 的新类型时,我只需要编写相关的结构并在包装器中添加一些代码。

My Question is that: How can I parse this JSON without any optional variable?我的问题是:如何在没有任何可选变量的情况下解析这个 JSON?

How I try to parse it:我如何尝试解析它:

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

Question is: How to get the nth element of a JSON Array when Decoding with Codable in Swift?问题是:在 Swift 中使用 Codable 进行解码时如何获取 JSON 数组的第 n 个元素?

What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..).我想要实现的是一个结构作为包装器和结构(Id、ServicePublicKey、Token 等)。 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.最后,当有来自 JSON 的新类型时,我只需要编写相关的结构并在包装器中添加一些代码。 My Question is that: How can I parse this JSON without any optional variable?我的问题是:如何在没有任何可选变量的情况下解析这个 JSON?

First of all I totally agree with your idea.首先我完全同意你的想法。 When decoding a JSON we should always aim to在解码 JSON 时,我们应该始终致力于

  • no optionals (as long as this is guaranteed by the backend)没有选项(只要后端保证)
  • easy extendibility易于扩展

Let's start开始吧

So given this 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)!

ID Model身份证型号

struct ID: Decodable {
    let id: Int
}

Token Model代币模型

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

ServerPublicKey Model服务器公钥模型

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

Result Model结果模型

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

Let's decode!让我们解码!

We can finally decode your JSON我们终于可以解码您的 JSON

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

Output输出

And this is the output这是输出

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

Date Decoding日期解码

I leave the date decoding to you as homework ;-)我把解码日期留给你作为作业;-)

UPDATE更新

This additional part should answer to your comment此附加部分应回答您的评论

Can we store variables like id, serverPublicKey inside Result struct without Response array.我们可以在没有响应数组的情况下将 id、serverPublicKey 等变量存储在 Result 结构中吗? I mean instead of ResponseArray can we just have properties?我的意思是我们可以只拥有属性而不是 ResponseArray 吗? I think It need a kind of mapping but I can't figure out.我认为它需要一种映射,但我无法弄清楚。

Yes, I think we can.是的,我认为我们可以。

We need to add one more struct to the ones already described above.我们需要在上面已经描述的结构中再添加一个结构。

Here it is这里是

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

This AccessibleResult struct has an initialiser which receives a Result value and tries to populated its 3 properties这个 AccessibleResult 结构有一个初始化器,它接收一个 Result 值并尝试填充它的 3 个属性

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

If everything goes fine, I mean if the input Result contains at least an ID , a Token and a ServerPublicKey then the AccessibleResponse is initialised, otherwise the init fails and nil` is returned.如果一切顺利,我的意思是如果输入Result至少包含一个ID 、一个Token和一个ServerPublicKeyAccessibleResponse被初始化,否则 init 失败并返回 nil`。

Test测试

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