7

我有一个 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 个元素?

4

1 回答 1

15

我想要实现的是一个结构作为(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"
        )
    )
])

日期解码

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

更新

此附加部分应回答您的评论

我们可以在没有响应数组的 Result 结构中存储 id、serverPublicKey 等变量吗?我的意思是我们可以只拥有属性而不是 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至少包含IDa 、 aToken和 aServerPublicKeyAccessibleResponse初始化,否则 init 失败并返回 nil`。

测试

if
    let result = try? JSONDecoder().decode(Result.self, from: data),
    let accessibleResult = AccessibleResult(result: result) {
        print(accessibleResult)
}
于 2020-01-11T14:41:58.873 回答