Search code examples
iosjsonswiftjsondecoderarrayofarrays

How to parse a JSON array of arrays and refer to the members of the inner array by names?


In a Swift Playground I try to parse the following data:

let jsonMoves:String =

"""
{ "moves":
    [
        [0, 'CAT (7)', 'ACT'],
        [1, 'EXTRA (14)', 'ERXT'],
        [0, 'TOP (22)', 'PO'],
        [1, 'TOY (9)', 'Y']
    ]
 }
"""

For that I create 2 structures:

struct MovesResponse: Codable {
    let moves: [[MoveModel]]
}

struct MoveModel: Codable {
    let mine: Int
    let words: String
    let letters: String
}

And the call:

let decoder = JSONDecoder()

if let movesData = jsonMoves.data(using: .utf8),
   let movesModel = try? decoder.decode(MovesResponse.self, from: movesData),
   movesModel.count > 0 // does not compile
{
    print("Parsed moves: ", movesModel)
} else {
    print("Can not parse moves")
}

Unfortunately, the above code gives me the compile error:

Value of type 'MovesResponse' has no member 'count'

And when I remove that line and also change the try? to try! to see the exception, then I get the error:

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around line 3, column 12." UserInfo={NSDebugDescription=Invalid value around line 3, column 12., NSJSONSerializationErrorIndex=29})))

Being a Swift newbie, I assume that the struct MoveModel is wrong. Please help.

Also I wonder if it is possible to refer to the three elements of the inner array as "mine", "words", "letters"?

UPDATE:

I have changed single quotes to double quotes in the jsonMoves as suggested by Joakim (thanks!) and the error is now:

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "moves", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))


Solution

  • You can use your MoveModel but since each of the inner arrays represent one instance of MoveModel you need to change your first struct to

    struct MovesResponse: Codable {
        let moves: [MoveModel]
    }
    

    and then you need a custom init(from:) in MoveModel that decodes each array to a MoveModel object using an unkeyed container instead of coding keys.

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        mine = try container.decode(Int.self)
        words = try container.decode(String.self)
        letters = try container.decode(String.self)
    }
    

    Rather than using try? and printing a hardcoded message I suggest you catch the error and print it

    let decoder = JSONDecoder()
    do {
      let movesModel = try decoder.decode(MovesResponse.self, from: data)
        print(movesModel)
    } catch {
        print(error)
    }