I'm writing a program where I'm parsing JSON
data that includes array of arrays, where the nested arrays have different object types (specifically, [[String, String, Int]]). For example,
{
"number": 5295,
"bets": [
[
"16",
"83",
9
],
[
"75",
"99",
4
],
[
"46",
"27",
5
]
]
}
I'm trying to use codable to help me parse the data, but when I try something like
struct OrderBook: Codable {
let number: Int
let bets: [Bet]
}
struct Bet: Codable {
let price: String
let sale: String
let quantity: Int
}
it gives me errors saying that
Expected to decode Dictionary
<String, Any>
but found an array instead
How do I get around this? I can't declare an array of empty type.
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
:)
来源:https://stackoverflow.com/questions/48002429/swift-codable-with-different-array-types