Reputation: 43
Please can someone help me point out what am doing wrong. I have searched other similar questions like this but non of it seems to help resolve this issue I am trying to decode json using Swift Codable
I have created nested codable struct, but it keeps failing.
The json am trying to decode can be found here - https://pastebin.com/C0QCREEF
This is the codable struct that is failing -
struct OutboundFlightData: Codable {
var data: OutboundFlight
}
struct OutboundFlight: Codable {
var outboundFlights: [OutboundFlightDetails]
}
struct OutboundFlightDetails: Codable {
var amount: String
var fareFamily: Bool
var cabin: String
var fareBasis: String
var duration: String
var itinerary: [OutboundFlightItinerary]
}
struct OutboundFlightItinerary: Codable {
var timeOfDeparture: String
var timeOfArrival: String
var dateOfDeparture: String
var dateOfArrival: String
var departureLocation: String
var departureDetails: FlightItineraryDetails
var arrivalLocation: String
var arrivalDetails: [FlightItineraryDetails]
var departureTerminal: String?
var arrivalTerminal: String?
var operatingCompany: String
var operatingDetails: FlightItineraryOperatingDetails
var marketingCompany: String
var marketingDetails: FlightItineraryOperatingDetails
var flightNumber: String
var electronicTicketing: String
}
struct FlightItineraryOperatingDetails: Codable {
var iata: String
var fullName: String
var countryName: String
var logoSmall: String
var logo: String
enum CodingKeys: String, CodingKey {
case iata
case fullName = "full_name"
case logoSmall = "logo_small"
case logo
case countryName = "country_name"
}
}
struct FlightItineraryDetails: Codable {
var iata: String
var name: String
var cityName: String
var stateName: String
var countryName: String
enum CodingKeys: String, CodingKey {
case iata
case name
case cityName = "city_name"
case stateName = "state_name"
case countryName = "country_name"
}
}
I expect the json to decode successfully. However I keep getting an error -
typeMismatch(
Swift.Array<Any>,
Swift.DecodingError.Context(
codingPath: [
CodingKeys(stringValue: "data", intValue: nil),
CodingKeys(stringValue: "outboundFlights", intValue: nil),
_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "itinerary", intValue: nil),
_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "arrivalDetails", intValue: nil)
],
debugDescription: "Expected to decode Array<Any> but found a dictionary instead.",
underlyingError: nil
)
)
But then when I change arrivalDetails from array. I get this error
typeMismatch(
Swift.Dictionary<Swift.String, Any>,
Swift.DecodingError.Context(
codingPath: [
CodingKeys(stringValue: "data", intValue: nil),
CodingKeys(stringValue: "outboundFlights", intValue: nil),
_JSONKey(stringValue: "Index 6", intValue: 6),
CodingKeys(stringValue: "itinerary", intValue: nil),
_JSONKey(stringValue: "Index 1", intValue: 1),
CodingKeys(stringValue: "arrivalDetails", intValue: nil)
],
debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.",
underlyingError: nil
)
)
Upvotes: 0
Views: 645
Reputation: 719
It looks like from the sample JSON you have provided, the arrivalDetails field is being filled either with a dictionary, or an empty array. That's the reason why when you switch between [FlightItineraryDetails]
and FlightItineraryDetails
, you get errors either way, because the decoder cannot parse a dictionary into an array, or an array into a dictionary.
This is unfortunately a case that Codable doesn't handle out of the box. You would need to make an enum for that case that tries to parse that key both ways, returning whichever one it found in the data.
(This code was shamelessly extracted from the output of feeding your JSON into https://app.quicktype.io. I always suggest using that for mapping JSON examples from APIs into Codable structures. It gets you at least 80% of the way there, and handles weird cases like this where APIs are not well-behaved.)
enum ArrivalDetails: Codable {
case anythingArray([JSONAny])
case details(Details)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([JSONAny].self) {
self = .anythingArray(x)
return
}
if let x = try? container.decode(Details.self) {
self = .details(x)
return
}
throw DecodingError.typeMismatch(ArrivalDetails.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ArrivalDetails"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .anythingArray(let x):
try container.encode(x)
case .details(let x):
try container.encode(x)
}
}
}
If you have control over the API that returns this result, you might consider changing that field to either be not present, or set to null if that field should be empty, instead of an empty array. Then you can simply change it back to [FlightItineraryDetails]?
.
Upvotes: 1