Unkeyed Container JSON Swift Decodable - json

I am trying to decode some JSON. Here is an example of the JSON:
[
{
"type": "departure",
"status": "landed",
"departure": {
"iataCode": "JFK",
"icaoCode": "KJFK",
"scheduledTime": "2017-12-11T01:06:00.000",
"estimatedRunway": "2017-12-11T02:07:00.000",
"actualRunway": "2017-12-11T02:07:00.000" },
"arrival": {
"iataCode": "CVG",
"icaoCode": "KCVG",
"estimatedRunway": "2017-12-11T03:38:00.000",
"actualRunway": "2017-12-11T03:38:00.000"
},
"airline": {
"name": "Atlas Air",
"iataCode": "5Y",
"icaoCode": "GTI"
},
"flight": {
"number": "302",
"iataNumber": "5Y302",
"icaoNumber": "GTI302"
}
},
{
//Same keys as above.
},
//Etc.
]
It begins as an unkeyed array. This is then follewed by JSON containers that are also unkeyed. I am having trouble breaking it apart using this code:
struct Dataset: Decodable {
var data: [FlightData]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
print(container)
data = [try container.decode(FlightData.self)]
}
struct FlightData: Decodable {
var type: String //Arrival or Departure
var status: String //Flight Status
var departure: Departure
var arrival: Arrival
var airline: Airline
var flight: Flight
struct Departure: Decodable {
var iataCode: String
var icaoCode: String
var terminal: String
var gate: String
var scheduledTime: String
var estimatedTime: String
var actualTime: String
var estimatedRunway: String
var actualRunway: String
}
struct Arrival: Decodable {
var iataCode: String
var icaoCode: String
var terminal: String
var gate: String
var baggage: String
var scheduledTime: String
var estimatedTime: String
var actualTime: String
var estimatedRunway: String
var actualRunway: String
}
struct Airline: Decodable {
var name: String
var iataCode: String
var icaoCode: String
}
struct Flight: Decodable {
var number: String
var iataNumber: String
var icaoNumber: String
}
}
}
Im new to JSON and Swift Decodable so I am a bit confused what I am doing wrong?
Does anyone know how I can fix my issue?
Right now I am getting the warning that it is expecting an array but it is finding a dictionary. Therefore, I think I have successfully gotten past the first unkeyed container but I cant get into the rest of it.

Remove your init method and then do
let decoder = JSONDecoder()
do {
data = try decoder.decode([FlightData].self, from: data)
} catch {
print(error)
}

Related

Can't seem to decode JSON

I know this type of question seems to be answered a lot but I really can't seem to make this work. I'm trying to decode some JSON data into my data structs. I think the problem is there. I may have my data model wrong, but can't quite work it out. The data is not an array, there is an array within it. Its trying to decode a dictionary into array but when I try to initialise my results variable as something other than array it won't build. I'll submit my code and the JSON data in the hopes someone can shed light!
The error I'm getting is:
JSON decode failed: Swift.DecodingError.typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
thank you so much
import SwiftUI
struct DataFormatted: Codable, Hashable {
var deliveryPoints: [DeliveryPoints]
var deliveryPointCount: Int
var postalCounty: String
var traditionalCounty: String
var town: String
var postCode: String
}
struct DeliveryPoints: Codable, Hashable {
var organisationName: String
var departmentName: String
var line1: String
var line2: String
var udprn: String
var dps: String
}
struct ContentView: View {
// I reckon the error is here:
#State private var results = [DataFormatted]()
var body: some View {
VStack{
List{
ForEach(results, id: \.self) { result in
Text(result.postalCounty)
}
}
}
.task {
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "https://pcls1.craftyclicks.co.uk/json/rapidaddress?key=APIKEY&postcode=aa11aa&response=data_formatted") else {
print("Invalid URL")
return
}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
do {
let (data, _) = try await URLSession.shared.data(for: request)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedResponse = try decoder.decode([DataFormatted].self, from: data)
results = decodedResponse
} catch let jsonError as NSError {
print("JSON decode failed: \(jsonError)")
}
}
}
JSON Data:
{
"delivery_points":[
{
"organisation_name":"THE BAKERY",
"department_name":"",
"line_1":"1 HIGH STREET",
"line_2":"CRAFTY VALLEY",
"udprn":"12345678",
"dps":"1A"
},
{
"organisation_name":"FILMS R US",
"department_name":"",
"line_1":"3 HIGH STREET",
"line_2":"CRAFTY VALLEY",
"udprn":"12345679",
"dps":"1B"
}
],
"delivery_point_count":2,
"postal_county":"POSTAL COUNTY",
"traditional_county":"TRADITIONAL COUNTY",
"town":"BIG CITY",
"postcode":"AA1 1AA"
}
try something like this:
struct DataFormatted: Codable {
var deliveryPoints: [DeliveryPoint]
var deliveryPointCount: Int
var postalCounty: String
var traditionalCounty: String
var town: String
var postcode: String // <-- postcode
}
struct DeliveryPoint: Codable {
var organisationName: String
var departmentName: String
var line1: String?
var line2: String?
var line3: String?
var udprn: String
var dps: String
}
and use it like this:
let apiResponse = try decoder.decode(DataFormatted.self, from: data)
EDIT-1: here is the code I used for testing:
// -- here, default values for convenience
struct DataFormatted: Codable {
var deliveryPoints: [DeliveryPoint] = []
var deliveryPointCount: Int = 0
var postalCounty: String = ""
var traditionalCounty: String = ""
var town: String = ""
var postcode: String = "" // <-- postcode
}
struct DeliveryPoint: Hashable, Codable { // <-- here
var organisationName: String
var departmentName: String
var line1: String?
var line2: String?
var line3: String?
var udprn: String
var dps: String
}
struct ContentView: View {
#State private var results = DataFormatted()
var body: some View {
VStack{
Text(results.postalCounty)
Text(results.town)
List {
ForEach(results.deliveryPoints, id: \.self) { point in
Text(point.organisationName)
}
}
}
.task {
await loadData()
}
}
func loadData() async {
let apikey = "your-key" // <-- here
guard let url = URL(string: "https://pcls1.craftyclicks.co.uk/json/rapidaddress?key=\(apikey)&postcode=aa11aa&response=data_formatted") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
results = try decoder.decode(DataFormatted.self, from: data) // <-- here
} catch {
print("JSON decode failed: \(error)")
}
}
}

Swift: Decode flat JSON to an structured object

wonder if there is a simple way to get a simple flat json to a struct with a structure
json:
{
"date": "2022-02-24T00:00:00.000Z",
"personel": 800,
"plane": 7,
"drone": 6,
}
what I want is to get a structure like this:
struct Day: Codable, Identifiable {
var id = UUID()
var date : String
var dayData: [DayData]
struct DayData: Codable {
var personel: Int
var plane: Int
var drone: Int
}
}
I thought there should be some simple way to make it work
As specified in comments it would be better to create temporary DBObject and initialize your Day object with it.
Simple DBObject:
struct DBObject: Decodable {
let date: String?
let personel, plane, drone: Int?
}
Adding init() to your Day
struct Day: Codable, Identifiable {
var id = UUID()
var date : String
var dayData: [DayData]
init(from dbObject: DBObject) {
let dayData = DayData(personel: dbObject.personel ?? 0,
plane: dbObject.plane ?? 0,
drone: dbObject.drone ?? 0)
self.dayData = [dayData]
self.date = dbObject.date ?? ""
}
struct DayData: Codable {
var personel: Int
var plane: Int
var drone: Int
}
}
Decoding Day from Data response:
func returnDay(from dataResponse: Data) -> Day {
do {
let decoder = JSONDecoder()
let dbObject = try decoder.decode(DBObject.self,
from: dataResponse)
return Day(from: dbObject)
} catch {
fatalError("Cannot decode object")
}
}

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

I am new to this. Somehow I am able to understand how to do this.
I am doing below, but it's giving error- The data couldn’t be read because it isn’t in the correct format.Can someone help me with this? I am stuck on this from past 4 days. I really appreciate.
import SwiftUI
import Foundation
import Combine
struct Movie: Decodable, Identifiable {
var id: Int
var video: String
var vote_count: String
var vote_average: String
var title: String
var release_date: String
var original_language: String
var original_title: String
}
struct MovieList: Decodable{
var results: [Movie]
___________
class NetworkingManager : ObservableObject{
var objectWillChange = PassthroughSubject<NetworkingManager, Never>()
#Published var movies = [Movie]()
init() {
load()
}
func load(){
let url = URL(string: "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>")!
URLSession.shared.dataTask(with: url){ (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([Movie].self, from: d)
DispatchQueue.main.async {
self.movies = decodedLists
}
}else {
print("No Data")
}
} catch {
print (error.localizedDescription)
}
}.resume()
}
}
This is how the response looks like:
{
"page": 1,
"results": [
{
"id": 419704,
"video": false,
"vote_count": 1141,
"vote_average": 6.2,
"title": "Ad Astra",
"release_date": "2019-09-17",
"original_language": "en",
"original_title": "Ad Astra",
"genre_ids": [
878
],
"backdrop_path": "/5BwqwxMEjeFtdknRV792Svo0K1v.jpg",
"adult": false,
"overview": "An astronaut travels to the outer edges of the solar system to find his father and unravel a mystery that threatens the survival of Earth. In doing so, he uncovers secrets which challenge the nature of human existence and our place in the cosmos.",
"poster_path": "/xJUILftRf6TJxloOgrilOTJfeOn.jpg",
"popularity": 227.167,
"media_type": "movie"
},
]
}
Code should fetch the data and hold in it the array I created. So that I can use it to display in the front end.
I had to consume the exact same API for a similar project and this is how I did it.
When calling:
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
It needs to match the same properties that the JSON response returns.
Below you'll see a MovieResponse struct and the Movie class, which will list all of the properties and return types that the JSON response returns.
The type adopts Codable so that it's decodable using a JSONDecoder instance.
See this official example for more information regarding Codable.
A type that can convert itself into and out of an external representation.
Provided they match then the JSONDecoder() will work to decode the data.
ContentView.swift:
struct ContentView: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var body: some View {
MovieList(movie: self.movieViewModel.movie)
}
}
MovieListViewModel.swift:
public class MovieListViewModel: ObservableObject {
public let objectWillChange = PassthroughSubject<MovieListViewModel, Never>()
private var movieResults: [Movie] = []
var movie: MovieResults = [Movie]() {
didSet {
objectWillChange.send(self)
}
}
func load(url: String = "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>") {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
DispatchQueue.main.async {
for movie in response.results {
self.movieResults.append(movie)
}
self.movie = self.movieResults
print("Finished loading Movies")
}
} catch {
print("Failed to decode: ", error)
}
}.resume()
}
}
MovieResponse.swift:
struct MovieResponse: Codable {
var page: Int
var total_results: Int
var total_pages: Int
var results: [Movie]
}
public class Movie: Codable, Identifiable {
public var popularity: Float
public var vote_count: Int
public var video: Bool
public var poster_path: String
public var id: Int
public var adult: Bool
public var backdrop_path: String
public var original_language: String
public var original_title: String
public var genre_ids: [Int]
public var title: String
public var vote_average: Float
public var overview: String
public var release_date: String
enum CodingKeys: String, CodingKey {
case popularity = "popularity"
case vote_count = "vote_count"
case video = "video"
case poster_path = "poster_path"
case id = "id"
case adult = "adult"
case backdrop_path = "backdrop_path"
case original_language = "original_language"
case original_title = "original_title"
case genre_ids = "genre_ids"
case title = "title"
case vote_average = "vote_average"
case overview = "overview"
case release_date = "release_date"
}
public init(popularity: Float, vote_count: Int, video: Bool, poster_path: String, id: Int, adult: Bool, backdrop_path: String, original_language: String, original_title: String, genre_ids: [Int], title: String, vote_average: Float, overview: String, release_date: String) {
self.popularity = popularity
self.vote_count = vote_count
self.video = video
self.poster_path = poster_path
self.id = id
self.adult = adult
self.backdrop_path = backdrop_path
self.original_language = original_language
self.original_title = original_title
self.genre_ids = genre_ids
self.title = title
self.vote_average = vote_average
self.overview = overview
self.release_date = release_date
}
public init() {
self.popularity = 0.0
self.vote_count = 0
self.video = false
self.poster_path = ""
self.id = 0
self.adult = false
self.backdrop_path = ""
self.original_language = ""
self.original_title = ""
self.genre_ids = []
self.title = ""
self.vote_average = 0.0
self.overview = ""
self.release_date = ""
}
}
public typealias MovieResults = [Movie]
MovieCellViewModel.swift:
public class MovieCellViewModel {
private var movie: Movie
public init(movie: Movie) {
self.movie = movie
}
public func getTitle() -> String {
return self.movie.title
}
// add more properties or functions here
}
MovieCell.swift:
struct MovieCell: View {
var movieCellViewModel: MovieCellViewModel
var body: some View {
Text(self.movieCellViewModel.getTitle())
}
}
MovieList.swift:
struct MovieList: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var movie: MovieResults
var body: some View {
List(self.movie) { movie in
MovieCell(movieCellViewModel: MovieCellViewModel(movie: movie))
}
}
}
I had a same error, but for the properties inside the struct for JSON object doesn't match JSON file. I fixed this error by setting the property names same as JSON file properties, and in the same order.
Swift file:
struct Coin: Decodable {
var asset_id_base: String
var rates: [CoinDetail]
}
struct CoinDetail: Decodable {
var time: String
var asset_id_quote: String
var rate: Double
}
JSON File:
{
"asset_id_base": "BTC",
"rates": [
{
"time": "2020-11-08T07:50:02.2865270Z",
"asset_id_quote": "CZK",
"rate": 328886.3419989546
},
{
"time": "2020-11-08T07:51:15.0750421Z",
"asset_id_quote": "GPL2",
"rate": 92123.4454168586
},
The Movie struct doesn't match the API response. You missed at least top level, like page and results array. Here you can paste your JSON answer and get the needed struct:
// MARK: - APIAnswer
struct APIAnswer: Codable {
let page: Int
let results: [Result]
}
// MARK: - Result
struct Result: Codable {
let id: Int
let video: Bool
let voteCount: Int
let voteAverage: Double
let title, releaseDate, originalLanguage, originalTitle: String
let genreIDS: [Int]
let backdropPath: String
let adult: Bool
let overview, posterPath: String
let popularity: Double
let mediaType: String
enum CodingKeys: String, CodingKey {
case id, video
case voteCount = "vote_count"
case voteAverage = "vote_average"
case title
case releaseDate = "release_date"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case posterPath = "poster_path"
case popularity
case mediaType = "media_type"
}
}
Then you need to use the top level struct, like:
// ...
let decodeResult = try JSONDecoder().decode([APIAnswer].self, from: d)
let movieList = decodeResult.results
// the other advice: don't just take answer and put it to the array.
// API can have errors too, so you can get the array with 2 equal id, for example
update checked your code for second time: you use
let decodeResult = try JSONDecoder().decode([Movie].self, from: d)
instead of:
let decodeResult = try JSONDecoder().decode(MovieList.self, from: d)
P.S. better to attach also the full error from Xcode in future

Deserialize double array

I try to deserialize some JSON on Swift 4.2 with Codable Protocol.
My Json:
{
"status":1,
"data":[
[
{
"id":"4klJeiCKTs",
"body":"first",
"da":"1442236233",
"dm":"1442236233"
},
{
...
}
]
]
}
my structures and code:
struct GetEntriesRequest: Decodable{
var status: Int
var data: [NestedArrayGetEntries]
}
struct NestedArrayGetEntries: Decodable{
var elements: [GetEntriesDataFromSession]
}
struct GetEntriesDataFromSession: Decodable{
var id: String
var body: String
var da: String
var dm: String
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(GetEntriesRequest.self, from: data)
dataSession = root
} catch { print(error) }
Also, I tried this struct
var data: [[GetEntriesDataFromSession]]
but without any success.
data key is a nested array and there is no elements key
var data: [NestedArrayGetEntries]
with
var data: [[NestedArrayGetEntries]]
struct GetEntriesRequest: Codable {
let status: Int
let data: [[NestedArrayGetEntries]]
}
struct NestedArrayGetEntries: Codable {
let id, body, da, dm: String
}
You don't also need a snakeCase here
do {
let root = try JSONDecoder().decode(GetEntriesRequest.self, from: data)
dataSession = root
} catch {
print(error)
}

My JSON decoder is not working and I'm not sure why

I'm trying to access a nested JSON variable called 'block' but I cannot seem to access it in any of the ways I've tried. Here's an example JSON message and my code:
{"account":"xrb_34tsctqcgctm8fhnpat351z4f64rgz8o9y7gwh1dutjf1r7iiwfzruawhatz","hash":"E5935C559748444D09E97D6D13FDB48B51F46A01FA9F6FB2DBD3576D684A53C6","block":"{\n \"type\": \"state\",\n \"account\": \"xrb_34tsctqcgctm8fhnpat351z4f64rgz8o9y7gwh1dutjf1r7iiwfzruawhatz\",\n \"previous\": \"78446816869EEEF4BC735B1A21AB33ED246A10303B87F0CAFD7CCD56406E0456\",\n \"representative\": \"xrb_3pczxuorp48td8645bs3m6c3xotxd3idskrenmi65rbrga5zmkemzhwkaznh\",\n \"balance\": \"320000000000000000000000000\",\n \"link\": \"8DE4EE799910E26C5E44CDD345B8C8070E1955284BC407660825B425FBEDBB6B\",\n \"link_as_account\": \"xrb_35h6xswsk694fjh6bmgmapwei3rg57ckiky61xm1ibfn6qxyugud9eo1fauk\",\n \"signature\": \"E4AF5BBDF583509DF3147004AB61FEC04F9007AC23A46A2E2E5BE4B65D0788F45F89EEC7B62D0F42144A9F5EA090EF3F58262070F07C59F1AD752B5CC3BF9D04\",\n \"work\": \"a56cb9e8d2539f73\"\n}\n","amount":"1`
struct IncomingBlock: Decodable {
var account: String
var hash: String
struct Block: Decodable {
var type: String
var previous: String
var link: String
var link_as_account: String
var representative: String
var account: String
var balance: String
var work: String
var signature: String
}
var block: Block
}
// in another file
guard let data = msg.data(using: .utf8) else { return }
guard let incomingBlock = try?JSONDecoder().decode(IncomingBlock.self, from: data) else { return }
Essentially to access the nested JSON variable block I had to decode the initial JSON message
do{
guard let data = inital_msg.data(using: .utf8) else { return }
let incomingBlock = try JSONDecoder().decode(IncomingBlock.self, from: data)
catch ...{}
and have the block's value cast to String in the model.
struct IncomingBlock: Decodable {
var account: String
var hash: String
var block: String
}
After that, I then decoded the initial messages block field once more like so
// Second JSON
let json = incomingBlock.block.data(using: .utf8)!
finally with the separated block model:
struct BlockMeta: Decodable {
var type: String
var previous: String
var link: String
var link_as_account: String
var representative: String
var account: String
var balance: String
var work: String
var signature: String
}
I could access the fields
let block = try JSONDecoder().decode(BlockMeta.self, from: json)
block.balance //returns "320000000000000000000000000"