Swift Codable with Different Array Types

那年仲夏 提交于 2019-12-01 08:28:10

As you already notice [[Any]] doesn't conform to Decodable protocol, so you will need to use JSONSerialization jsonObject(with: Data) method to create a custom initializer for your struct:

struct OrderBook {
    let number: Int
    let bets: [Bet]
    init(data: Data) throws {
        let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
        number = dict["number"] as! Int
        bets = (dict["bets"] as! [[Any]]).map {
            Bet(price: $0[0] as! String, sale: $0[1] as! String, quantity:  $0[2] as! Int)
        }
    }
}
struct Bet {
    let price: String
    let sale: String
    let quantity: Int
}

Testing:

let json = """
{"number": 5295,
 "bets": [
    ["16","83",9],
    ["75","99",4],
    ["46","27",5]
        ]
}
"""
let data = Data(json.utf8)

do {
    let orderBook = try OrderBook(data: data)
    print(orderBook)
} catch {
    print(error)
}

This will print

OrderBook(number: 5295, bets: [Bet(price: "16", sale: "83", quantity: 9), Bet(price: "75", sale: "99", quantity: 4),Bet(price: "46", sale: "27", quantity: 5)])

One solution (assuming you can't change the JSON) is to implement custom decoding logic for Bet. You can use an unkeyed container (which reads from a JSON array) in order to decode each of the properties in turn (the order in which you call decode(_:) is the order they're expected to appear in the array).

import Foundation

struct OrderBook : Codable {
  let number: Int
  let bets: [Bet]
}

struct Bet : Codable {
  let price: String
  let sale: String
  let quantity: Int

  init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    self.price = try container.decode(String.self)
    self.sale = try container.decode(String.self)
    self.quantity = try container.decode(Int.self)
  } 

  // if you need encoding (if not, make Bet Decodable
  // and remove this method)
  func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    try container.encode(price)
    try container.encode(sale)
    try container.encode(quantity)
  }
}

Example decoding:

let jsonString = """
{ "number": 5295, "bets": [["16","83",9], ["75","99",4], ["46","27",5]] }
"""

let jsonData = Data(jsonString.utf8)

do {
  let decoded = try JSONDecoder().decode(OrderBook.self, from: jsonData)
  print(decoded)
} catch {
  print(error)
}

// OrderBook(number: 5295, bets: [
//   Bet(price: "16", sale: "83", quantity: 9),
//   Bet(price: "75", sale: "99", quantity: 4),
//   Bet(price: "46", sale: "27", quantity: 5)
// ])

It's bad json structure, if you can change it form server, I would suggest:

{
    "number": 5295,
    "bets": [
       {
          "price": "16",
          "sale": "83",
          "quantity": 9
       }
       ....
    ]
}

But if you cannot change that json format, I think you should use the SwiftyJSON library to handle json. I hate Codable :)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!