0
votes

This is my json I am trying to decode using JSONDecoder but having hard time as its not able to do it. can someone help with what struct and how can I decode?

{
"Afghanistan": [
    {
        "city": "Kabul",
        "lat": "34.5167",
        "lng": "69.1833",
        "state": "Kābul",
        "country": "Afghanistan"
    },
    {
        "city": "Karukh",
        "lat": "34.4868",
        "lng": "62.5918",
        "state": "Herāt",
        "country": "Afghanistan"
    },
    {
        "city": "Zarghūn Shahr",
        "lat": "32.85",
        "lng": "68.4167",
        "state": "Paktīkā",
        "country": "Afghanistan"
    }
],
"Albania": [
    {
        "city": "Tirana",
        "lat": "41.3275",
        "lng": "19.8189",
        "state": "Tiranë",
        "country": "Albania"
    },

    {
        "city": "Pukë",
        "lat": "42.0333",
        "lng": "19.8833",
        "state": "Shkodër",
        "country": "Albania"
    }
]}

what do you suggest to decode it?

I am trying this

let locationData: Countries = load("Countries.json")

func load<T: Decodable>(_ filename: String) -> T {
let data: Data

guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
    fatalError("Couldn't find \(filename) in main bundle.")
}

do {
    data = try Data(contentsOf: file)
} catch {
    fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}

do {
    let decoder = JSONDecoder()
    return try decoder.decode(T.self, from: data)
} catch {
    fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
 }

struct Country: Codable, Identifiable {
var id = UUID()

let city, lat, lng: String
let state: String?
let country: String
}

typealias Countries = [String: [Country]]

But getting this error

Couldn't parse Countries.json as Dictionary>: keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Albania", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil)):

2
Unrelated to your question, but throwing fatalError is usually a bad idea, especially in the catch block of a do-catch. If you really cannot recover from an error thrown by try, you should simply use try!. However, in most cases that's a bad idea as well, since if that catch block ever gets executed for a user, the app will simply crash without them getting an error message. It's best to use assertionFailures instead of fatalErrora so that you catch errors early during development, but users never experience crashes and display them a generic error message instead.Dávid Pásztor

2 Answers

1
votes

Since the property id is not part of the json you need to define what properties the decoder should decode by adding a CodingKey enum to Country

enum CodingKeys: String, CodingKey {
    case city, lat, lng, state, country
}

The CodingKey enum also gives you the opportunity to use better names for your struct properties if you want to and map them in the enum to the json keys.

struct Country: Codable, Identifiable {
    var id = UUID()

    let city: String
    let latitude: String
    let longitude: String
    let state: String?
    let country: String

    enum CodingKeys: String, CodingKey {
        case city
        case latitude = "lat"
        case longitude = "lng"
        case state, country
    }
}
1
votes

This is the relevant part of the error: keyNotFound(CodingKeys(stringValue: "id", intValue: nil). It's telling you that it's looking for an "id" key in each JSON object and isn't finding it.

It is looking for an id because the default implementation of the Codable protocol for structs tries to deserialize all properties you define. You defined a var id property, so it's looking for that.

Since you don't want to deserialize the id property, you need to customize your struct so that it doesn't use the default implementation. The documentation for how to do that is here: Encoding and Decoding Custom Types

In your case you just need to define a CodingKeys enum inside your struct so that it knows which keys to look for:

enum CodingKeys: String, CodingKey {
    case city, lat, lng, state, country
}