4

I have some data passed via JSON which is an array of tuples containing arrays, and I can't extract the data out of it. I've tried to cast to different types, but the only thing that works is [Any] and I can't break that down further. The JSON:

{
  ...
  // stuff that I can easily read, like "WIDTH": 33,
  ... 
  "WIDTHS_ROI_PAIRS": [[80, [0,0,200,160]], [145, [0, 240, 100, 60]], [145, [100, 240, 100, 60]]]
}

The struct it is meant to go into:

struct WidthRoiPair: Codable {
    let width: Int
    let roi: [Int]
}

What I want to do (it doesn't work, take it as pseudo-code):

let widthRoiPairsTmp = json["WIDTHS_ROI_PAIRS"] as! [Any]
for p in widthRoiPairsTmp {
    let pair = WidthRoiPair(width: p.0 as! Int, roi:p.1 as! [Int])
    widthRoiPairs.append(pair)
}

Trying p[0] instead of p.0 also doesn't work, trying to cast the JSON directly to what I need, something like this:

let widthRoiPairsTmp = json["WIDTHS_ROI_PAIRS"] as! [(Int, [Int])]

also doesn't work. I tried to use JSONDecoder() but I don't know how to pass json["WIDTHS_ROI_PAIRS"] (or its elements) to it (how to convert it back to Data). I'm sure the answer is obvious to anyone with a little more experience with Swift, but at the moment I'm totally stuck...

thebucc
  • 207
  • 1
  • 10

1 Answers1

2

You can try

struct Root: Codable {
    let widthsRoiPairs: [[InnerItem]]

    enum CodingKeys: String, CodingKey {
        case widthsRoiPairs = "WIDTHS_ROI_PAIRS"
    }
}

enum InnerItem: Codable {
    case integer(Int)
    case integerArray([Int])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(Int.self) {
            self = .integer(x)
            return
        }
        if let x = try? container.decode([Int].self) {
            self = .integerArray(x)
            return
        }
        throw DecodingError.typeMismatch(InnerItem.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for InnerItem"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .integer(let x):
            try container.encode(x)
        case .integerArray(let x):
            try container.encode(x)
        }
    }
}

Usage

let str = """

            {
            "WIDTHS_ROI_PAIRS": [[80, [0,0,200,160]], [145, [0, 240, 100, 60]], [145, [100, 240, 100, 60]]]
            }

"""

    do {

        let res = try JSONDecoder().decode(Root.self, from: str.data(using: .utf8)!)

        res.widthsRoiPairs.forEach {

            $0.forEach {

                switch $0 {
                case .integer(let w) :
                    print(w)
                case .integerArray(let arr) :
                    print(arr)
                }

            }
        }

    }
    catch {

        print(error)
    }
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • Thanks, that worked perfectly. I'm surprised there aren't more straightforward ways to parse data with a little bit of structure: having to write your own decoder just for that seems a bit too much. – thebucc Nov 28 '18 at 15:52
  • @thebucc the better is to change the json structure from `[80, [0,0,200,160]]` to `[[80], [0,0,200,160]]` so you can consider it nested Int array , you should know also that Codable go complex hand in hand with complexity of json – Shehata Gamal Nov 28 '18 at 20:09
  • you're right @Sh_Khan, then I can define widthsRoiPairs: [[[Int]]], removing the need for the InnerItem class and the extra code for encoding/decoding (by the way I'm amazed at how quickly you put it together). The json format is carried over from other code, and is more intuitive, but I might consider changing it to simplify the code. Anyway, it's a shame that we can't use tuples defining widthsRoiPairs: [(Int, [Int])] – thebucc Nov 30 '18 at 11:02