9

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.

5
  • may it should be like (specifically, [[Bet(String, String, Int)]]) or {"number":5295,"bets":[[Bet(price: "", sale: "", quantity: 54)]]} .. Or may be I didnt get your question Commented Dec 28, 2017 at 6:20
  • 1
    You array type it is [[Any]] not [[String, String, Int]] Commented Dec 28, 2017 at 6:26
  • Right, I'm aware. But when I try to put let bets: [[Any]] it says it "doesn't conform to protocol 'Decodable'" Commented Dec 28, 2017 at 6:34
  • I think you will need to use JSONSerialization to decode your data Commented Dec 28, 2017 at 6:36
  • 1
    Foundation can allow for arrays of ostensibly different types by converting between objects of the same superclass. This question may help you: stackoverflow.com/questions/24236492/… Commented Dec 28, 2017 at 6:37

1 Answer 1

10

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)
// ])
Sign up to request clarification or add additional context in comments.

1 Comment

This is the right answer. You can do whatever you want with Codable. It is very customizable. You just need to know how to use it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.