Swift 4 + Alamofire Decodable Json URL format - swift

I have a JSON format which I do not decodable with Alamofire.
Here is my json:
"data":[
{
"id":37,
"status":"A\u00e7\u0131k",
"department":"Muhasebe",
"title":"Y\u00f6netim Panelinden Deneme 4 - Mail Kontrol",
"message":"<p>Y\u00f6netim Panelinden Deneme 4 - Mail Kontrol<br><\/p>",
"file":null,
"created_at":{
"date":"2018-01-13 01:59:49.000000",
"timezone_type":3,
"timezone":"UTC"
},
"replies":[
{
"id":6,
"ticket_id":37,
"admin_id":null,
"user_id":8593,
"message":"<p>test<\/p>",
"file":"uploads\/tickets\/8593-P87wd8\/GFV6H5M94y5Pt27YAxZxHNRcVyFjD554i80og3xk.png",
"created_at":"2018-01-18 11:16:55",
"updated_at":"2018-01-18 11:16:55"
}
]
},
Here is my model for the JSON:
struct TeknikDestek : Decodable {
var id: Int?
var status: String?
var title: String?
var department: String?
var message: String?
var replies: [Replies]?
}
struct Replies: Decodable {
var replyid: Int?
var ticket_id: Int?
var admin_id: Int?
var user_id: Int?
var message: String?
}
I called it Alamofire, but it does not come back when I do response.data.
Alamofire.request("https://myurl.com.tr/api/tickets/\(userid)").responseJSON { (response) in
switch response.result {
case .success:
if((response.result) != nil) {
let jsonData = response.data
print("jsonData: \(test)")
do{
self.allReplies = try JSONDecoder().decode([TeknikDestek].self, from: jsonData!)
print(self.allReplies)
for reply in self.allReplies {
print("Reply: \(reply)")
}
}catch {
print("Error: \(error)")
}
self.view.dismissNavBarActivity()
}
case .failure(let error):
print(error)
}
}
This is the error console:
How can I make it work? I've spent several hours now but without success. Please help me. Many Thanks.

The question is not related to Alamofire. It's only related to JSONDecoder / Decodable
You need an umbrella struct for the root object, which is a dictionary containing the data key, not an array. That's what the error message states.
struct Root : Decodable {
let data : [TeknikDestek]
}
Then decode Root
let root = try JSONDecoder().decode(Root.self, from: jsonData!)
and get the replies with
self.allReplies = root.data.first?.replies // returns `nil` if data is empty
Note: It's highly recommended to name data structs in singular form (e.g. Reply), semantically you have a collection of singular items

Related

Having trouble parsing received data from SignalR

When trying to parse the data received, the values are nil, I can see the received data in the debug log, but just cannot parse the data.
I am using
https://github.com/moozzyk/SignalR-Client-Swift
self.chatHubConnection!.on(method: "SendMessage", callback: { (payload: ArgumentExtractor?) in
let response = try! payload?.getArgument(type: SignalR?.self)
print("Response: \(response!)")
})
Model
struct SignalR: Codable {
let type: Int?
let target: String?
let arguments: [Argument]?
}
struct Argument: Codable {
let id: ID
enum CodingKeys: String, CodingKey {
case id = "_id"
}
}
struct ID: Codable {
let timestamp, machine, pid, increment: Int
let creationTime: Date
}
Use do try catch so you can get a better error instead of a crash.
self.chatHubConnection!.on(method: "SendMessage", callback: { (payload: ArgumentExtractor?) in
do{
let response = try payload?.getArgument(type: SignalR?.self)
print("Response: \(response!)")
}catch{
print(error)
}
})

Issues with Codable Arrays and Alamofire

I am attempting to submit the following request through Alamofire and I am receiving the following error:
2020-01-13 09:41:05.912103-0600 AFNetworkingDemo[29720:604258] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'
My assumption is that it is the way I am defining the arrays within the object (I was following some of the material found here: https://benscheirman.com/2017/06/swift-json)
struct ProgramRequest: Codable {
var userID: Int
var programData: ProgramData
var json: Constants.Json {
return [
"userID": userID,
"programData": programData.json,
]
}
}
struct ProgramData: Codable {
var airhotel: [AirHotel]
var security: [Security]
var json: Constants.Json {
return [
"airhotel": airhotel,
"security": security
]
}
}
struct AirHotel: Codable {
var id: Int
var loyaltyNumber: String
var json: Constants.Json {
return [
"id": id,
"loyaltyNumber": loyaltyNumber
]
}
}
struct Security: Codable {
var vendorCode: String
var loyaltyNumber: String
var json: [String: Any] {
return [
"vendorCode": vendorCode,
"loyaltyNumber": loyaltyNumber
]
}
}
The json dictionary at each level is to render the objects appropriately for Alamofire. For a given example, if I print it out using:
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(programRequest)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString) #1
}
print("IsValidJSON: ", JSONSerialization.isValidJSONObject(programRequest)) #2
print(programRequest.json) #3
urlRequest = try! JSONEncoding.default.encode(urlRequest, with: programRequest.json) #4 causes error
1 output:
{
"userID" : 10021,
"programData" : {
"airhotel" : [],
"security" : [
{
"vendorCode" : "sty",
"loyaltyNumber" : "Loyal1"
}
]
}
}
2 output
IsValidJSON: false
3 output - I noticed the AFNetworkingDemo.Security within the output, is that what Alamofire JSONEncoding on:
["programData": ["security": [AFNetworkingDemo.Security(vendorCode: "sty", loyaltyNumber: "Loyal1")], "airhotel": []], "userID": 10021]
My question would be, what changes to I need to make in the AirHotel and Security sections of ProgramRequest in order to resolve my issues with Alamofire?
Step 1: Stop reinventing Codable
The json computed property is not needed and wrongly used.
Remove it.
struct ProgramRequest: Codable {
var userID: Int
var programData: ProgramData
}
struct ProgramData: Codable {
var airhotel: [AirHotel]
var security: [Security]
}
struct AirHotel: Codable {
var id: Int
var loyaltyNumber: String
}
struct Security: Codable {
var vendorCode: String
var loyaltyNumber: String
}
Step 2: Start using Codable
Now given a programRequest, eg. like this
let programRequest = ProgramRequest(userID: 1, programData: ProgramData(airhotel: [AirHotel(id: 1, loyaltyNumber: "loyaltyNumber")], security: [Security(vendorCode: "loyaltyNumber", loyaltyNumber: "loyaltyNumber")]))
You can generate the correct Data value representing the JSON simply writing
let data = try JSONEncoder().encode(programRequest)
And (if you want) you can convert data intro a String
let json = String(data: data, encoding: .utf8)
Output
Optional("{\"userID\":1,\"programData\":{\"airhotel\":[{\"id\":1,\"loyaltyNumber\":\"loyaltyNumber\"}],\"security\":[{\"vendorCode\":\"loyaltyNumber\",\"loyaltyNumber\":\"loyaltyNumber\"}]}}")
That's it

Facing problem with JSON parsing in swift

My REST returns following Array, and only one item.
{
"Table1": [
{
"Id": 1,
"ClauseNo": "2-111",
"Title": "Testing Title",
"URL": "http://www.google.com",
}
]
}
I'm trying to use the Codable as following:
struct Clause: Codable {
var Id: Int
var ClauseNo: String
var Title: String
var URL: String
}
What I'm doing wrong with following code?
func parse(json: Data) -> Clause {
var clause: Clause?
if let jsonClause = try? JSONDecoder().decode([Clause].self, from: json) {
clause = jsonClause
}
return clause!
}
As I mentioned above, I only have 1 item not more than that.
This is very common mistake, you are ignoring the root object
struct Root : Decodable {
private enum CodingKeys : String, CodingKey { case table1 = "Table1" }
let table1 : [Clause]
}
struct Clause: Decodable {
private enum CodingKeys : String, CodingKey { case id = "Id", clauseNo = "ClauseNo", title = "Title", url = "URL" }
let id: Int
let clauseNo: String
let title: String
let url: URL
}
...
func parse(json: Data) -> Clause? {
do {
let result = try JSONDecoder().decode(Root.self, from: json)
return result.table1.first
} catch { print(error) }
return nil
}
Side note: Your code crashes reliably if an error occurs
I tend to handle these scenarios like this:
struct Table1 : Codable {
var clauses: [Clause]
struct Clause: Codable {
let Id: Int // variable names should start with a lowercase
let ClauseNo: String // letter :)
let Title: String
let URL: String
}
}
And then when you're decoding you end up with a table from which you want the first element, something like:
if let jsonTable = try? JSONDecoder().decode(Table1.self, from: json) {
clause = jsonTable.clauses[0]
}

Parse Codable classes and avoid repetition

I have a JSON response as the following:
{
"http_status": 200,
"success": true,
"has_error": false,
"error": [
""
],
"response": {
"token": "",
"verified": false,
"message": ""
}
}
As far as i can say for an app API usage http_status, success, has_error, error are shared between all APIS and thus i will create a Codable class to handle it, but the response could be different model, so here is what I'm trying to do.
I have created a general response class as the below, so this class i can use for all apis in the project to avoid duplication of same class but different names:
class GeneralResponse:Codable {
let http_status: Int?
let success, has_error: Bool?
let error: [String]?
enum CodingKeys: String, CodingKey {
case http_status = "http_status"
case success = "success"
case has_error = "has_error"
case error = "error"
}
init(http_status: Int?, success: Bool?, has_error: Bool?,error: [String]?) {
self.http_status = http_status
self.success = success
self.has_error = has_error
self.error = error
}
}
Now i have created the response class which will handle for now the registration response:
class RegistrationResponseDetails: Codable {
let token: String?
let verified: Bool?
let message: String?
init(token: String?, verified: Bool?, message: String?) {
self.token = token
self.verified = verified
self.message = message
}
}
And lets say i need to parse the registration the response so here is what i did, i have created a class and used both of them:
class RegistrationResponse: Codable {
let generalResponse:GeneralResponse?
let response: RegistrationResponseDetails?
init(generalResponse: GeneralResponse?, response: RegistrationResponseDetails?) {
self.generalResponse = generalResponse
self.response = response
}
}
So i will mainly use RegistrationResponse to parse the response which will parse "generalResponse" which includes http_status, success, has_error, error, and then response will parse the desired response object.
But at some point generalResponse object is always nil and response has the data parsed correctly, what should i do to make generalResponse get parsed without duplication in each api, because in each api i will have generalResponse object so is it possible to solve it ?
Note: I'm using Alamofire as the networking library.
You can make your GeneralResponse generic and tell it what type to use when parsing the response:
class GeneralResponse<T: Codable>: Codable {
let http_status: Int?
let success, has_error: Bool?
let error: [String]?
let response: T?
}
class RegistrationResponseDetails: Codable {
let token: String?
let verified: Bool?
let message: String?
}
Then you can give it the inner response class when you parse the json:
let generalResponse = try JSONDecoder().decode(GeneralResponse<RegistrationResponseDetails>.self, from: jsonData)
// generalResponse.response is of type RegistrationResponseDetails?
First of all if
http_status, success, has_error, error are shared between all APIS
why are the class properties optional?
If the mentioned keys are the same but the value for key response is different use generics.
In most cases structs are sufficient.
struct JSONParser<T : Decodable> {
struct Response<U : Decodable> : Decodable {
let httpStatus: Int
let success, hasError: Bool
let error: [String]
let response : U
}
let responseData : Response<T>
init(data: Data) throws {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
responseData = try decoder.decode(Response.self, from: data)
}
}
Then create the different structs e.g.
struct RegistrationResponseDetails : Decodable {
let token: String
let verified: Bool
let message: String
}
and parse it
let parser = try JSONParser<RegistrationResponseDetails>(data: data)
let registrationResponseDetails = parser.responseData.response
For a simple case
class Main:Decodable {
let name:String? // insert all shared vars
}
class Sub:Main {
let id:String?
}
This will parse
{
"name" : "rr" ,
"id" : "oo"
}

Parse complex json code

I have the following JSON code and want to parse it in Swift. I use Alamofire to get the JSON and have created a struct for the parsing:
{
"-8802586561990153106-1804221538-5":{
"zug":{
"klasse":"RB",
"nummer":"28721"
},
"ankunft":{
"zeitGeplant":"1804221603",
"zeitAktuell":"1804221603",
"routeGeplant":[
"Wiesbaden Hbf",
"Mainz Hbf"
]
},
"abfahrt":{
"zeitGeplant":"1804221604",
"zeitAktuell":"1804221604",
"routeGeplant":[
"Gro\u00df Gerau",
"Klein Gerau",
"Weiterstadt"
]
}
},
"8464567322535526441-1804221546-15":{
"zug":{
"klasse":"RB",
"nummer":"28724"
},
"ankunft":{
"zeitGeplant":"1804221657",
"zeitAktuell":"1804221708",
"routeGeplant":[
"Aschaffenburg Hbf",
"Mainaschaff"
]
},
"abfahrt":{
"zeitGeplant":"1804221658",
"zeitAktuell":"1804221709",
"routeGeplant":[
"Mainz-Bischofsheim"
]
}
}
}
I have created a struct for this that looks like this:
struct CallResponse: Codable {
struct DirectionTrain: Codable {
struct Train: Codable {
let trainClass: String
let trainNumber: String
}
struct Arrival: Codable {
let line: String
let eta: Date
let ata: Date
let platform: String
let route: [String]
}
struct Departure: Codable {
let line: String
let etd: Date
let atd: Date
let platform: String
let route: [String]
}
}
}
The rest of my code is:
Alamofire.request(url!).responseJSON { response in
switch response.result {
case .success:
let decoder = JSONDecoder()
let parsedResult = try! decoder.decode(CallResponse.self, from: response.data!)
case .failure(let error):
print(error)
}
}
When I run this code the error message is:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "train", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"train\", intValue: nil) (\"train\").", underlyingError: nil))
Can anyone help me find my problem? Thank you for your answers!
The problem is merely that your structs look nothing at all like your JSON!
Your JSON is a dictionary whose keys have names like "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15". But I don't see you declaring any struct that deals with those keys.
Then each of those turns out to be a dictionary with keys like "zug", "ankunft", and "abfahrt". But I don't see you declaring any struct that deals with those keys either.
And then the "zug" has keys "klasse" and "nummer"; you don't have those either.
And so on.
Either your structs must look exactly like your JSON, or else you must define CodingKeys and possibly also implement init(from:) to deal with any differences between your structs and your JSON. I suspect that the keys "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15" are unpredictable, so you will probably have to write init(from:) in order to deal with them.
For example, I was able to decode your JSON like this (I do not really recommend using try!, but we decoded without error and it's just a test):
struct Entry : Codable {
let zug : Zug
let ankunft : AnkunftAbfahrt
let abfahrt : AnkunftAbfahrt
}
struct Zug : Codable {
let klasse : String
let nummer : String
}
struct AnkunftAbfahrt : Codable {
let zeitGeplant : String
let zeitAktuell : String
let routeGeplant : [String]
}
struct Top : Decodable {
var entries = [String:Entry]()
init(from decoder: Decoder) throws {
struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
let con = try! decoder.container(keyedBy: CK.self)
for key in con.allKeys {
self.entries[key.stringValue] =
try! con.decode(Entry.self, forKey: key)
}
}
}
// d is a Data containing your JSON
let result = try! JSONDecoder().decode(Top.self, from: d)