Swift Decoding JSON from Amadeus - swift

I have a problem with decoding JSON from API.
I get JSON:
{
"meta": {
"count": 1,
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations?subType=AIRPORT&keyword=barcel&sort=analytics.travelers.score&view=LIGHT&page%5Boffset%5D=0&page%5Blimit%5D=10"
}
},
"data": [
{
"type": "location",
"subType": "AIRPORT",
"name": "AIRPORT",
"detailedName": "BARCELONA/ES:AIRPORT",
"id": "ABCN",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/ABCN",
"methods": [
"GET"
]
},
"iataCode": "BCN",
"address": {
"cityName": "BARCELONA",
"countryName": "SPAIN"
}
}
]
}
My code looks like this:
struct Airport: Decodable {
let meta: MetaAirport
let data: AirportInfo
}
struct Address: Decodable{
let countryName: String
let cityName: String
}
struct AirportInfo: Decodable{
let address: Address
let subType: String
let id: String
let type: String
let detailedName: String
let name: String
let iataCode: String
}
struct MetaAirport : Decodable{
let count: Int
let links: Links
}
struct Links : Decodable{
let info: String
private enum CodingKeys : String, CodingKey {
case info = "self"
}
}
I'm trying to decode json using JSONDecoder on an AirportInfo object
do {
let airport = try JSONDecoder().decode(Airport.self, from: data!)
print(airport.data.name)
}
catch let err {
print(err)
}
I get an error
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
What am I doing wrong?

In json data, address is a object. Change your code:
let address: [Address] -> let address: Address

Your Model should be like that to parse
struct DataModel: Codable {
let meta: Meta?
let data: [Datum]?
}
struct Datum: Codable {
let type, subType, name, detailedName: String?
let id: String?
let datumSelf: SelfClass?
let iataCode: String?
let address: Address?
enum CodingKeys: String, CodingKey {
case type, subType, name, detailedName, id
case datumSelf = "self"
case iataCode, address
}
}
struct Address: Codable {
let cityName, countryName: String?
}
struct SelfClass: Codable {
let href: String?
let methods: [String]?
}
struct Meta: Codable {
let count: Int?
let links: Links?
}
struct Links: Codable {
let linksSelf: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
}
}

I see you have edited your question from the original, so it feels hard to correctly identify at a given moment in time where the issue is.
However, Codable is great and one easy way for you to work through it is by constructing your object little by little. There is no requirement for an object to identically represent your JSON IF you make things optional. So when debugging this kind of issues, make different properties optional and see what got parsed. This would allow you to identify little by little where you're doing things wrong, without causing crashes and keeping the correctly formatted parts.
Also, pro tip: If you plan to keep this code for anything more than 1 week, stop using force unwrapping ! and get used to handle optionals correctly. Otherwise this is a hard habit to stop and others will hate the code you've created ;)

Related

Having problems mapping down the json data

I am having problems with mapping down some json data that are coming from the API, A lot of errors are coming and I am really having a difficult time dealing with this one since I am new to swift.
Structure:
{
"StatusCode": 0,
"Result": [
{
"Type": "Test",
"Date": "Test",
"Message": "Test"
},
{
"Type": "Test",
"Date": "Test",
"Message": "Test"
}
]
}
My Structure That is not Working:
struct Notifications: Decodable {
struct NotificationsStructure: Decodable {
var Type: String?
var Date: String?
var Message: String?
}
var StatusCode: Int
var Result: NotificationsStructure!
}
First of all never declare a property in a Decodable object as implicit unwrapped optional. If it can be missing or be nil declare it as regular optional (?) otherwise non-optional.
In JSON there are only two collection types:
dictionary represented by {},
array represented by []
The value for key Result is clearly an array.
And add CodingKeys to map the capitalized keys to lowercase property names
struct Notifications: Decodable {
struct NotificationsStructure: Decodable {
private enum CodingKeys: String, CodingKey {
case type = "Type", date = "Date", message = "Message"
}
let type: String
let date: String
let message: String
}
private enum CodingKeys: String, CodingKey {
case statusCode = "StatusCode", result = "Result",
}
let statusCode: Int
let result: [NotificationsStructure]
}
The response from result is an array type . You should create another object that have same properties of each array.
struct NotificationResponse : Decodable {
let StatusCode : Int
let Result: [Notification]
}
struct Notification : Decodable {
let Type: String
let Date: String
let Message: String
}

Swift optimize structs [duplicate]

This question already has answers here:
Can we reuse struct on Swift? Or is there any other way?
(4 answers)
Closed 1 year ago.
Assuming I have two API calls
// /someurl/api/product-list
{
"status":0,
"message": "ok",
"data":{
"items":[
{
"id":1,
"name":"iPhone",
"desc":{
"en-us":"This is an iPhone",
"fr":"Ceci est un iPhone"
}
}
]
}
}
// /someurl/api/movie-list
{
"status":0,
"message": "ok",
"data":{
"items":[
{
"imdb_id":"tt0081505",
"title":"The Shining",
"short_desc":{
"en-us":"This is The Shining",
"fr":"C'est le Shining"
}
}
]
}
}
Now, both two api responses include the same structure of status, message(potentially would have pagination info object), except the data are different.
And In productModel
struct ProcuctAPIResponse: Codable {
let status: Int
let data: ProcuctAPIResponseData
let message: String?
}
struct ProcuctAPIResponseData: Codable {
let items: [Product]
}
struct Product: Codable {
let id: Int
let name: String?
let desc: [String: String]?
}
And In movieModel
struct MovieAPIResponse: Codable {
let status: Int
let data: MovieAPIResponseData
let message: String?
}
struct MovieAPIResponseData: Codable {
let items: [Movie]
}
struct Movie: Codable {
let imdb_id: Int
let title: String?
let short_desc: [String: String]?
}
My question is that is there a way I can create a BaseAPIResponse
something like
struct BaseAPIResponse: Codable {
let status: Int
let message: String?
}
then ProcuctAPIResponse and MovieAPIResponse can be extended from of it?
If not, how do you optimize these codes?
use protocols
protocol BaseAPIResponse: Codable {
let status: Int
let message: String?
}
Now implement the structs:
struct ProcuctAPIResponse: BaseAPIResponse {...}
movieModel
struct movieModel: BaseAPIResponse {...}

Deserialize JSON array based on nested type attribute

Consider this example JSON:
{
"sections": [{
"title": "Sign up",
"rows": [
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
},
{
"type": "textField",
"placeholder": "confirmPassword"
},
{
"type": "button",
"placeholder": "Register!"
}
]
}]
}
Let's say I wanted to parse the JSON above into the following models (I know it doesn't compile due to the Row protocol not corresponding to Decodable):
enum RowType: String, Codable {
case textField
case image
case button
}
protocol Row: Codable {
var type: RowType { get }
}
struct TextFieldRow: Row {
let type: RowType
let placeholder: String
let value: String
enum CodingKey: String {
case type
case placeholder
case value
}
}
struct ImageRow: Row {
let type: RowType
let imageURL: URL
enum CodingKey: String {
case type
case imageURL
}
}
struct ButtonRow: Row {
let type: RowType
let title: String
enum CodingKey: String {
case type
case title
}
}
struct Section: Codable {
let rows: [Row]
let title: String
enum CodingKey: String {
case rows
case title
}
}
struct Response: Codable {
let sections: [Section]
enum CodingKey: String {
case sections
}
}
// Parsing the response using the Foundation JSONDecoder
let data: Data // From network
let decoder = JSONDecoder()
do {
let response = try decoder.decode(Response.self, from: data)
} catch {
print("error: \(error)")
}
Is there a way to make the Swift code above Codable compliant?
I know you can manually solve this by first grabbing each Row's type string and then creating the right type of Row model as well as changing them from structs to classes and letting the Row protocol be a superclass instead. But is there a way that requires less manual labour?
Using an enum with associated value is the best option:
Consider this enum:
enum Row: Decodable {
case textField(TextFieldRow)
case image(ImageRow)
// and other cases
case unknown
enum CodingKeys: String, CodingKey {
case type
}
public init(from decoder: Decoder) throws {
do {
let selfContainer = try decoder.singleValueContainer()
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
let type = try typeContainer.decode(String.self, forKey: .type)
switch type {
case "textField": self = .textField( try selfContainer.decode(TextFieldRow.self) )
case "Image": self = .image( try selfContainer.decode(ImageRow.self) )
// and other cases
default: self = .unknown
}
}
}
}
With these changes:
struct TextFieldRow: Decodable {
let placeholder: String?
let value: String?
}
struct ImageRow: Decodable {
let imageURL: URL
}
// and so on
Now this will decode like a charm:
// Minmal testing JSON
let json = """
[
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
}
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
print( try! decoder.decode([Row].self, from: json) )
You can now add any other case you need to the decoder to build your application builder app.

Decoding this simple JSON struct is not working

I have a simple model which I defined to decode a struct.
But it is failing at decoding.
Can any one tell me what i am doing wrong?
struct Model: Codable {
let firstName: String
let lastName: String
let age: Int
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let session = URLSession.shared
let url = URL(string: "https://learnappmaking.com/ex/users.json")!
let task = session.dataTask(with: url) { (data, response, error) in
let decoder = JSONDecoder()
let d = try! decoder.decode([Model].self, from: data!) //fails here
print(d)
}
task.resume()
}
}
I double checked to see if the json was correct, but it still fails to decode.
Error shown
Thread 5: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "firstName",
intValue: nil), Swift.DecodingError.Context(codingPath:
[_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No
value associated with key CodingKeys(stringValue: \"firstName\",
intValue: nil) (\"firstName\").", underlyingError: nil))
It keeps searching for firstName but i specifically have a enum to check for first_name.
This is the JSON Payload
[
{
"first_name": "Ford",
"last_name": "Prefect",
"age": 5000
},
{
"first_name": "Zaphod",
"last_name": "Beeblebrox",
"age": 999
},
{
"first_name": "Arthur",
"last_name": "Dent",
"age": 42
},
{
"first_name": "Trillian",
"last_name": "Astra",
"age": 1234
}
]
I know I can add decoder.keyDecodingStrategy = .convertFromSnakeCase but I want to know why the existing code is not working?
The code is correct, but apparently there is some problem with your model (although convertFromSnakeCase does work)
I retyped the struct and the error went away. Please copy and paste this
struct Model : Decodable {
let firstName : String
let lastName : String
let age : Int
private enum CodingKeys : String, CodingKey { case firstName = "first_name", lastName = "last_name", age }
}
Some of the values are optional, to be safe make all let as optional, It will work for sure.
struct Model: Codable {
let firstName: String?
let lastName: String?
let age: Int?
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}

Swift Codable with Different Array Types

I'm writing a program where I'm parsing JSON data that includes array of arrays, where the nested arrays have different object types (specifically, [[String, String, Int]]). For example,
{
"number": 5295,
"bets": [
[
"16",
"83",
9
],
[
"75",
"99",
4
],
[
"46",
"27",
5
]
]
}
I'm trying to use codable to help me parse the data, but when I try something like
struct OrderBook: Codable {
let number: Int
let bets: [Bet]
}
struct Bet: Codable {
let price: String
let sale: String
let quantity: Int
}
it gives me errors saying that
Expected to decode Dictionary<String, Any> but found an array instead
How do I get around this? I can't declare an array of empty type.
One solution (assuming you can't change the JSON) is to implement custom decoding logic for Bet. You can use an unkeyed container (which reads from a JSON array) in order to decode each of the properties in turn (the order in which you call decode(_:) is the order they're expected to appear in the array).
import Foundation
struct OrderBook : Codable {
let number: Int
let bets: [Bet]
}
struct Bet : Codable {
let price: String
let sale: String
let quantity: Int
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
self.price = try container.decode(String.self)
self.sale = try container.decode(String.self)
self.quantity = try container.decode(Int.self)
}
// if you need encoding (if not, make Bet Decodable
// and remove this method)
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(price)
try container.encode(sale)
try container.encode(quantity)
}
}
Example decoding:
let jsonString = """
{ "number": 5295, "bets": [["16","83",9], ["75","99",4], ["46","27",5]] }
"""
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(OrderBook.self, from: jsonData)
print(decoded)
} catch {
print(error)
}
// OrderBook(number: 5295, bets: [
// Bet(price: "16", sale: "83", quantity: 9),
// Bet(price: "75", sale: "99", quantity: 4),
// Bet(price: "46", sale: "27", quantity: 5)
// ])