-3

I've looked at many similar questions on this, but I just can't grok what I'm missing.

My JSON looks like this:

   {
    "Stations": [{
 {
        "Code": "A02",
        "Name": "Farragut North",
        "StationTogether1": "",
        "StationTogether2": "",
        "LineCode1": "RD",
        "LineCode2": null,
        "LineCode3": null,
        "LineCode4": null,
        "Lat": 38.903192,
        "Lon": -77.039766,
        "Address": {
            "Street": "1001 Connecticut Avenue NW",
            "City": "Washington",
            "State": "DC",
            "Zip": "20036"
        }
    }, {
        "Code": "A03",
        "Name": "Dupont Circle",
        "StationTogether1": "",
        "StationTogether2": "",
        "LineCode1": "RD",
        "LineCode2": null,
        "LineCode3": null,
        "LineCode4": null,
        "Lat": 38.909499,
        "Lon": -77.04362,
        "Address": {
            "Street": "1525 20th St. NW",
            "City": "Washington",
            "State": "DC",
            "Zip": "20036"
        }

I have set up a Struct:

struct AllStations : Codable {

let stations: [String]?
let code: String?
let name: String?

let lat: Double?
let lon: Double?
let lineCode1: String?
let lineCode2: String?
let lineCode3: String?
let lineCode4: String?
let together1: String?
let together2: String?
let address: [String]?
let street: String?
let city: String?
let state: String?
let zip: String?

private enum CodingKeys: String, CodingKey {
    case stations = "Stations"
    case code = "Code"
    case name = "Name"

    case lat = "Lat"
    case lon = "Lon"
    case lineCode1 = "LineCode1"
    case lineCode2 = "LineCode2"
    case lineCode3 = "LineCode3"
    case lineCode4 = "LineCode4"
    case together1 = "StationTogether1"
    case together2 = "StationTogether2"
    case address = "Address"
    case street = "Street"
    case city = "City"
    case state = "State"
    case zip = "Zip"

}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    let response = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .stations)

    stations = try response.decode([String].self, forKey: .stations)

    code = try response.decode(String.self, forKey: .code)
    name = try response.decode(String.self, forKey: .name)

    lat = try response.decode(Double.self, forKey: .lat)
    lon = try response.decode(Double.self, forKey: .lon)
    lineCode1 = try response.decode(String.self, forKey: .lineCode1)
    lineCode2 = try response.decode(String.self, forKey: .lineCode2)
    lineCode3 = try response.decode(String.self, forKey: .lineCode3)
    lineCode4 = try response.decode(String.self, forKey: .lineCode4)
    together1 = try response.decode(String.self, forKey: .together1)
    together2 = try response.decode(String.self, forKey: .together2)
    address = try response.decode([String].self, forKey: .address)

    let anAddress = try response.nestedContainer(keyedBy: CodingKeys.self, forKey: .address)
    street = try anAddress.decode(String.self, forKey: .street)
    city = try anAddress.decode(String.self, forKey: .city)
    state = try anAddress.decode(String.self, forKey: .state)
    zip = try anAddress.decode(String.self, forKey: .zip)

}

}

I'm retrieving data from a URL and passing this to my Decoder:

func processResponse(using data: Data?) {

        if let jsonData = data
        {
            let decoder = JSONDecoder()

            do {

                let allStations = try decoder.decode(AllStations.self, from: jsonData)

                print(#function, "A Station: ", allStations as Any)
            } catch {
                print(error.localizedDescription)
            }
        } else {
            // Respond to error
        }
    }

But I've got a formatting issue:

The data couldn’t be read because it isn’t in the correct format.

halfer
  • 19,824
  • 17
  • 99
  • 186
ICL1901
  • 7,632
  • 14
  • 90
  • 138
  • _"The data couldn’t be read because it isn’t in the correct format."_ means that "roughly" isn't sufficient to diagnose this. – jscs Feb 15 '19 at 20:03
  • ok, fixing it .... – ICL1901 Feb 15 '19 at 20:04
  • Don't just dump the whole JSON document here, please. Find the smallest piece of your input data that reproduces the problem, and if it isn't obvious at that point what the issue is, then include _that_ in the question – jscs Feb 15 '19 at 20:05
  • Please see if this is ok.. And thanks for helping.. – ICL1901 Feb 15 '19 at 20:06
  • 1
    the updated json isn't correct – Shehata Gamal Feb 15 '19 at 20:08
  • I missed the first line.. Sorry – ICL1901 Feb 15 '19 at 20:09
  • 2
    **Never ever** print `error.localizedDescription` when catching `Decoding` errors. You get this quite meaningless error message. Print the `error` instance (`print(error)`) to get a comprehensive precise error message. – vadian Feb 15 '19 at 20:09
  • I had a similar issue awhile back - possibly the discussion on my question will help you: https://stackoverflow.com/questions/47174672/how-to-extract-data-from-nested-json-with-swift-4-with-dynamic-keys – jessi Feb 15 '19 at 20:10
  • That isn't valid JSON, so if that's really your real input data, then it's bad and your code is correctly refusing to decode an object from it. – jscs Feb 15 '19 at 20:13
  • By the way, `decode` method will throw an error on `null`. You would need `decodeIfPresent` to handle nulls. Better not implement that `init` method at all. – Sulthan Feb 15 '19 at 20:14
  • @Sulthan. Yep, there will be lots of null elements. What do use use if I remove the `init` – ICL1901 Feb 15 '19 at 20:21

2 Answers2

1

It's obvious that value of "Stations" is not actually an array of strings.

You need separate nested objects:

struct Station: Decodable {
    let code: String?
    let name: String?

    let lat: Double?
    let lon: Double?
    let lineCode1: String?
    let lineCode2: String?
    let lineCode3: String?
    let lineCode4: String?
    let together1: String?
    let together2: String?
    let address: Address?

    private enum CodingKeys: String, CodingKey {
        case code = "Code"
        case name = "Name"

        case lat = "Lat"
        case lon = "Lon"
        case lineCode1 = "LineCode1"
        case lineCode2 = "LineCode2"
        case lineCode3 = "LineCode3"
        case lineCode4 = "LineCode4"
        case together1 = "StationTogether1"
        case together2 = "StationTogether2"
        case address = "Address"
    }
}

And

struct AllStations: Decodable {
    let stations: [Station]

    private enum CodingKeys: String, CodingKey {
        case stations = "Stations"
    }
}

The Address itself must be another nested object:

struct Address: Decodable {
    let street: String?
    let city: String?
    let state: String?
    let zip: String?

    private enum CodingKeys: String, CodingKey {
        case street = "Street"
        case city = "City"
        case state = "State"
        case zip = "Zip"
    }
} 
Sulthan
  • 128,090
  • 22
  • 218
  • 270
1

You can do this step to get rid of the main root useless struct

do {
  let tr = try JSONSerialization.jsonObject(with:data) as! [String:Any] 
  let staData = try JSONSerialization.data(withJSONObject:tr["Stations"]!, options:[]) 
  let allStations = try JSONDecoder().decode([Station].self, from:staData) 
} 
catch {
   print(error)
}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87