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

埃米尔·恩德

我有一个由顶级对象组成的JSON,然后是由不同JSON对象组成的数组。我想用最少的结构并且没有可选变量来解码此json。如果可以实现,我还想设计一个结构,通过仅编写其相关结构来处理所有数组对象。

我将尝试简化示例

JSON图片

如您在图像中看到的,“ Id”,“ Token”,“ ServicePublicKey”都是不同的JSON对象。我的整个后端都返回这种JSON架构。我要实现的是将一个结构作为包装和结构(Id,ServicePublicKey,令牌等)。最后,当有来自JSON的新类型时,我只需要编写相关的struct并在包装器中添加一些代码。

我的问题是:如何在没有任何可选变量的情况下解析此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个元素?

卢卡·安格莱蒂(Luca Angeletti)

我要实现的是将一个结构作为包装和结构(Id,ServicePublicKey,令牌等)。最后,当有来自JSON的新类型时,我只需要编写相关的struct并在包装器中添加一些代码。我的问题是:如何在没有任何可选变量的情况下解析此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
}

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

日期解码

我把日期解码留给您做功课;-)

更新

此额外部分应回答您的评论

我们可以在没有Response数组的情况下将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,aToken和a ServerPublicKeyAccessibleResponse则初始化,否则初始化失败并返回nil`。

测试

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何使用 Codable 解析不同对象列表的 Json 数组?

json模式如何代表具有不同对象的数组?

如何在Swift中合并不同对象的数组

如何对具有不同对象的数组中的所有值使用 .some()

如何使用 Codable 解码字典的 JSON 字典(具有不同的键)?

如何从json创建具有不同对象类型的列表

自定义 JSON 解码:解码多个不同数组中的不同对象数组

如何在Swift 4.1和XCode 9.3中使用JSONDecoder解码嵌套的JSON数组和对象?

Swift Codable:使用相同的根对象解码不同的项目数组

在 Swift 4 中,如何使用 Codable 解码 JSON 并在解码的对象之间创建引用?

如何在Swift中解析具有对象的JSON数组

有没有一种简单的方法可以通过golang解码json数组中的不同对象?

具有不同对象的反序列化json数组-Jackson / Spring

Java Parse Json 与具有不同对象类型(Gson 或 Jackson 等)的数组

对具有不同对象的数组列表进行排序

在TypeScript中具有不同对象的数组

如何在数组中插入带有数组的不同对象

如何在 Swift 中使用 Codable 解析 JSON?

特定的 JSON 解析问题 - 不同对象的数组 - Swift

如何在Swift中解码JSON对象的命名数组

使用 swift 的 Codable 解码 JSON

如何在PHP中使用json解码访问多维数组内的对象?

如何反序列化具有包含不同对象的属性的json?

仅使用Swift 4 Codable解码JSON数据中的数组

如何创建具有不同引用的相同对象的副本?

如何在两个不同对象的基类中使用模板化类

使用类的相同对象,但具有不同的成员

使用Codable解码具有相似键的嵌套JSON

如何在javascript中解决此代码?将具有相同值的不同对象组合成单个数组