Search code examples
iosjsonswiftdecodablejsondecoder

JSONDecoder can't decode Array but can decode String


I have a struct that I would like to parse from Json:

struct Subsidiary: Decodable {
    let id: Int?
    let subsidiary_ref: String?
    let name: String?
    let address: String?
}

I try to parse it like:

let sub: [Subsidiary] = try! JSONDecoder().decode([Subsidiary].self, from: data)

where data is Data type from

session.dataTask(with: urlRequest) { (data, response, error) in

and it gives me an error

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a string/data instead.", underlyingError: nil))

If I do it like this:

let sub: String = try! JSONDecoder().decode(String.self, from: data)

it works and it gives me son

[
   {"id": 5913, "subsidiary_ref": "0000159", "name": "Mercator Hipermarket Koper", "address": "Poslov.98-Koper,Dolinska C", "city": "Koper", "coordinates_x": null, "coordinates_y": null, "phone_number": "+386 56-636-800", "frequency": "A", "channel": "Food", "subchannel": "Hypermarket", "customer": 7, "day_planned": true, "badge_visible": true, "open_call": true, "number_of_planned": 13, "number_of_calls": 22, "last_call_day": 3, "notes": " notesi ki ne smejo nikoli zginiti bla marko bdsa"},
    {"id": 5870, "subsidiary_ref": "0000773", "name": "Kompas Shop Pe Ferneti?i", "address": "Partizanska 150", "city": "Se?ana", "coordinates_x": null, "coordinates_y": null, "phone_number": "+386 57-380-636", "frequency": "A", "channel": "Food", "subchannel": "Supermarket", "customer": 17, "day_planned": true, "badge_visible": true, "open_call": true, "number_of_planned": 13, "number_of_calls": 1, "last_call_day": 1, "notes": null},...
]

What is wrong with my code?

EDIT:

I figure out, that if I create a string variable, and then convert this string value to data it works even with Subsidiary struct.

So something should be wrong with the data variable.


Solution

  • The fact that you can decode a String.self and get your JSON printed means that the root of the JSON is a string. The JSON you got, in text form, probably looks like this:

    "[\r\n   {\"id\": 5913, \"subsidiary_ref\": \"0000159\", \"name\": \"Mercator Hipermarket Koper\", \"address\": \"Poslov.98-Koper,Dolinska C\", \"city\": \"Koper\", \"coordinates_x\": null, \"coordinates_y\": null, \"phone_number\": \"+386 56-636-800\", \"frequency\": \"A\", \"channel\": \"Food\", \"subchannel\": \"Hypermarket\", \"customer\": 7, \"day_planned\": true, \"badge_visible\": true, \"open_call\": true, \"number_of_planned\": 13, \"number_of_calls\": 22, \"last_call_day\": 3, \"notes\": \" notesi ki ne smejo nikoli zginiti bla marko bdsa\"}\r\n]"
    

    Note that the actual JSON you want is put in ""s, so all the special characters are escaped.

    The web service has given the JSON in a very weird way...

    To get the actual JSON data decoded, you need to get the string first, then turn the string into Data, then decode that Data again:

    // nil handling omitted for brevity
    let jsonString = try! JSONDecoder().decode(String.self, from: data)
    let jsonData = jsonString.data(using: .utf8)!
    let sub = try! JSONDecoder().decode([Subsidiary].self, from: jsonData)
    

    Of course, the best solution is to fix the backend.