Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Codable with Different Array Types

Tags:

swift

codable

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.

like image 950
Nat Avatar asked Dec 28 '17 06:12

Nat


People also ask

Are Swifts Codable arrays?

A lot of Swift's built-in types already conform to Codable by default. For example, Int , String , and Bool are Codable out of the box. Even dictionaries and arrays are Codable by default as long as the objects that you store in them conform to Codable .

What types are Codable Swift?

There are many types in Swift that are codable out of the box: Int , String , Date , Array and many other types from the Standard Library and the Foundation framework. If you want your type to be codable, the simplest way to do it is by conforming to Codable and making sure all its stored properties are also codable.

Can a class conform to Codable?

If all the properties of a type already conform to Codable , then the type itself can conform to Codable with no extra work – Swift will synthesize the code required to archive and unarchive your type as needed.


1 Answers

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)
// ])
like image 52
Hamish Avatar answered Oct 22 '22 06:10

Hamish