I make a request to the OpenSky Network API and get a result like this:
{
"time":1629739170,
"states":[
["4b1800","SWR1076 ","Switzerland",1629739169,1629739169,8.5493,47.4581,373.38,false,79.14,275.97,7.15,null,487.68,null,false,0],
["3c65c4","DLH35A ","Germany",1629739170,1629739170,6.5185,46.2346,11590.02,false,255.57,48.84,0,null,11986.26,"1000",false,0]
]
}
Now I want to decode that JSON string to a list of FlightState
objects, but there are no keys provided. From the API documentation (see link above) I know what value corresponds to what property, but how do I create corresponding Swift objects?
The following doesn't work, obviously, because there are no keys.
let decoder = JSONDecoder()
let flightState: FlightState = try! decoder.decode(FlightState.self, from: dataString.data(using: .utf8)!)
struct FlightState: Codable {
let time: Int
let states: [StateVector]
struct StateVector: Codable {
let icao24: UUID
let callsign: String
let origin_country: String
let time_position: Int
let last_contact: Int
let longitude: Float
let latitude: Float
let baro_altitude: Float
let on_ground: Bool
let velocity: Float
let true_track: Float
let vertical_rate: Float
let sensors: [Int]
let geo_altitude: Float
let squawk: String
let spi: Bool
let position_source: Int
}
}
The error message says Expected to decode Dictionary<String, Any> but found an array instead.
So how do I tell Swift to interpret the values in the states
lists as properties of objects with their corresponding type?
I have the feeling that someone must have been stumbled upon this before, but I couldn't find a solution. Maybe I am too new to Swift to recognize a solution as such.
You can decode a JSON where specific properties are held in specific array indices rather than under specific Dictionary
keys by implementing init(from decoder:)
on your own and using an unkeyedContainer()
to decode the array, then calling decode
to decode each property in order.
Bear in mind lots of properties in the response can be null
, so you need to declare those as Optional
and use decodeIfPresent
rather than decode
.
struct FlightState: Decodable {
let time: Int
let states: [StateVector]
struct StateVector: Decodable {
let icao24: String
let callSign: String
let originCountry: String
let timePosition: Int?
let lastContact: Int
let longitude: Float?
let latitude: Float?
let baroAltitude: Float?
let onGround: Bool
let velocity: Float?
let trueTrack: Float?
let verticalRate: Float?
let sensors: [Int]?
let geoAltitude: Float?
let squawk: String?
let spi: Bool
let positionSource: Int
init(from decoder: Decoder) throws {
var values = try decoder.unkeyedContainer()
self.icao24 = try values.decode(String.self)
self.callSign = try values.decode(String.self)
self.originCountry = try values.decode(String.self)
self.timePosition = try values.decodeIfPresent(Int.self)
self.lastContact = try values.decode(Int.self)
self.longitude = try values.decodeIfPresent(Float.self)
self.latitude = try values.decodeIfPresent(Float.self)
self.baroAltitude = try values.decodeIfPresent(Float.self)
self.onGround = try values.decode(Bool.self)
self.velocity = try values.decodeIfPresent(Float.self)
self.trueTrack = try values.decodeIfPresent(Float.self)
self.verticalRate = try values.decodeIfPresent(Float.self)
self.sensors = try values.decodeIfPresent([Int].self)
self.geoAltitude = try values.decodeIfPresent(Float.self)
self.squawk = try values.decodeIfPresent(String.self)
self.spi = try values.decode(Bool.self)
self.positionSource = try values.decode(Int.self)
}
}
}