Swift : Codable Struct Always Nil - json

I Have the following JSON Response
{
"status_code": 1000,
"data": {
"user_id": 1000,
"bid": "E5PPD5E3",
"province": 0,
"location": "123,123"
},
"message": "Verified"
}
And This is my Struct
struct Basicresponse : Codable{
var statusCode : Int!
var message : String?
var data : data?
enum CodingKeys: String, CodingKey {
case statusCode = "status_code"
}
}
struct data : Codable{
var province : Int
var userID : Int
var location : String
var bid : String
enum CodingKeys: String, CodingKey {
case province, location , bid
case userID = "user_id"
}
}
And
do {
let jsonData = try JSONDecoder().decode(Basicresponse.self, from: data!)
if(jsonData.statusCode == 1000){
print(jsonData)
}else{
self.alert.show(target: self.view, message: jsonData.message!)
}
}
catch let jsonerr {
print("error serrializing error",jsonerr)
}
But the result as below,
Basicresponse(statusCode: Optional(2000), message: nil, data: nil)
I don't know why both the data and the message are always nil ?! I tried the end point with Post man and its works fine but in my app its always nil, Am i missing something here ?
Any help will be much appreciated

The issue is that you’ve excluded message and data from your CodingKeys. But you can add them like so:
struct Basicresponse: Codable {
var statusCode : Int!
var message : String?
var data : data?
enum CodingKeys: String, CodingKey {
case statusCode = "status_code"
case message, data
}
}
The other alternative is to not supply CodingKeys at all and tell your decoder to do the snake case conversion for you.
let data = """
{
"status_code": 1000,
"data": {
"user_id": 1000,
"bid": "E5PPD5E3",
"province": 0,
"location": "123,123"
},
"message": "Verified"
}
""".data(using: .utf8)!
struct BasicResponse: Codable {
var statusCode: Int
var message: String?
var data: Bid?
}
struct Bid: Codable {
var province: Int
var userId: Int
var location: String
var bid: String
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let jsonData = try decoder.decode(BasicResponse.self, from: data)
if jsonData.statusCode == 1000 {
print(jsonData)
} else {
print(jsonData.message ?? "No message")
}
} catch let jsonError {
print("error serializing error", jsonError)
}
I hope you don’t mind, but I’ve renamed your data type to be Bid (as data doesn’t conform to standard class naming conventions of starting with upper case letter and it’s too easily confused with the existing Data type). I don’t know if Bid is the right name, so use whatever you think is appropriate, but hopefully it illustrates the idea.

Related

Swift Data Model from JSON Response

I am running into an issue building the correct data model for the following JSON response.
{
"resources": [
{
"courseid": 4803,
"color": "Blue",
"teeboxtype": "Championship",
"slope": 121,
"rating": 71.4
},
{
"courseid": 4803,
"color": "White",
"teeboxtype": "Men's",
"slope": 120,
"rating": 69.6
},
{
"courseid": 4803,
"color": "Red",
"teeboxtype": "Women's",
"slope": 118,
"rating": 71.2
}
]
}
Here is the current model. No matter what I do I can't seem to get the model populated. Here is also my URL session retrieving the data. I am new to Swift and SwiftUI so please be gentle. I am getting data back however I am missing something.
import Foundation
struct RatingsResources: Codable {
let golfcourserating : [GolfCourseRating]?
}
struct GolfCourseRating: Codable {
let id: UUID = UUID()
let courseID: Int?
let teeColor: String?
let teeboxtype: String?
let teeslope: Double?
let teerating: Double?
enum CodingKeysRatings: String, CodingKey {
case courseID = "courseid"
case teeColor = "color"
case teeboxtype
case teeslope = "slope"
case teerating = "rating"
}
}
func getCoureRating(courseID: String?) {
let semaphore = DispatchSemaphore (value: 0)
print("GETTING COURSE TEE RATINGS..........")
let urlString: String = "https://api.golfbert.com/v1/courses/\(courseID ?? "4800")/teeboxes"
print ("API STRING: \(urlString) ")
let url = URLComponents(string: urlString)!
let request = URLRequest(url: url.url!).signed
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let response = try? JSONDecoder().decode([RatingsResources].self, from: data) {
DispatchQueue.main.async {
self.ratingresources = response
}
return
}
print("*******Data String***********")
print(String(data: data, encoding: .utf8)!)
print("***************************")
let ratingsData: RatingsResources = try! decoder.decode(RatingsResources.self, from: data)
print("Resources count \(ratingsData.golfcourserating?.count)")
semaphore.signal()
}
task.resume()
semaphore.wait()
} //: END OF GET COURSE SCORECARD
First of all, never use try? while decoding your JSON. This will hide all errors from you. Use try and an appropriate do/catch block. In the catch block at least print the error.
Looking at your model there seem to be three issues here.
You don´t have an array of RatingsResources in your array. It is just a single instance.
let response = try JSONDecoder().decode(RatingsResources.self, from: data)
RatingsResources is not implemented correct.
let golfcourserating : [GolfCourseRating]?
should be:
let resources: [GolfCourseRating]?
Your coding keys are implemented wrong instead of:
enum CodingKeysRatings: String, CodingKey {
it should read:
enum CodingKeys: String, CodingKey {
You should add enum CodingKey with resources at struct RatingsResources
And decode:
if let response = try? JSONDecoder().decode(RatingsResources.self, from: data) {
// Your response handler
}

Unable to GET from JSON API

I have tried following a variety of tutorials, and I am unable to progress on getting data from this API. I did manage to succeed on a simpler JSON ], but this one is eating up my time.
First, the JSON:
{
"object": {
"array": [
{
"id": 48,
"name": "Job No.# 48",
"description": "blah",
"start_at": "2021-03-05T13:15:00.000+11:00",
"end_at": "2021-03-05T14:15:00.000+11:00",
"map_address": "blah road"
},
{
"id": 56,
"name": "Job No.# 56",
"description": "Do it",
"start_at": "2021-06-22T11:30:00.000+10:00",
"end_at": "2021-06-22T13:30:00.000+10:00",
"map_address": " blah"
}
],
"person": {
"id": 52,
"first_name": "Bob",
"last_name": "Newby",
"mobile": "0401111111",
"email": "bob#mail.com"
}
}
}
And now my attempt at decoding it:
struct api_data: Codable {
let object : Object
}
struct Object: Codable {
let array : [array]
let person : Person
}
struct array: Codable, Identifiable {
let id : Int?
let start_at, end_at : Date?
let duration : Float?
let cancellation_type : String?
let name, description, address, city, postcode, state : String?
}
struct Person: Codable, Identifiable {
let id : Int?
let first_name, last_name, mobile, email : String?
}
class FetchShifts: ObservableObject {
#Published var shifts = [Shifts]()
init() {
let url = URL(string: "realURLhiddenForPrivacy")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("myToken", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) {(data, response, error) in
do {
if let array_data = data {
let array_data = try JSONDecoder().decode([array].self, from: array_data)
DispatchQueue.main.async {
self.array = array_data
}
} else {
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
And how I attempt to present it:
#ObservedObject var fetch = FetchArray()
var body: some View {
VStack {
List(array.shifts) { shft in
VStack(alignment: .leading) {
Text(shft.name!)
}
}
}
}
}
}
Any help is appreciated, not sure where it is I go wrong here, been at it for 5-7 hours going through tutorials.
I always recommend using app.quicktype.io to generate models from JSON if you're unfamiliar with it. Here's what it yields:
// MARK: - Welcome
struct Welcome: Codable {
let status: String
let payload: Payload
}
// MARK: - Payload
struct Payload: Codable {
let shifts: [Shift]
let worker: Worker
}
// MARK: - Shift
struct Shift: Codable, Identifiable {
let id: Int
let name, shiftDescription, startAt, endAt: String
let mapAddress: String
enum CodingKeys: String, CodingKey {
case id, name
case shiftDescription = "description"
case startAt = "start_at"
case endAt = "end_at"
case mapAddress = "map_address"
}
}
// MARK: - Worker
struct Worker: Codable {
let id: Int
let firstName, lastName, mobile, email: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
case mobile, email
}
}
Then, to decode, you'd do:
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shift_data)
let shifts = decoded.payload.shifts
} catch {
print(error)
}
Note that in Swift, it's common practice to use camel case for naming, not snake case, so you'll see that CodingKeys does some conversion for that (there are automated ways of doing this as well).
Update, based on comments:
Your code would be:
if let shiftData = data {
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shiftData)
DispatchQueue.main.async {
self.shifts = decoded.payload.shifts
}
} catch {
print(error)
}
}
Instead of defining custom keys, you can automatically use
keyDecodingStrategy = .convertFromSnakeCase for your JSON decoder, and could specify custom date format or even throw a custom error in your decoder implementation.
struct Worker: Codable {
let id: Int
let firstName: String?
let lastName: String?
let mobile: String?
let email: String?
}
struct Shift: Codable, Identifiable {
let id: Int
let name: String?
let description: String?
let startAt: Date?
let endAt: Date?
let mapAddress: String?
}
struct Payload: Codable {
let shifts: [Shift]?
let worker: Worker?
}
struct Response: Codable {
let status: String
let payload: Payload?
}
class MyCustomDecoder: JSONDecoder {
override init() {
super.init()
self.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
self.dateDecodingStrategy = .formatted(dateFormatter)
}
}
// Usage
if let data = json.data(using: .utf8) {
let response = try MyCustomDecoder().decode(Response.self, from: data)
print(response)
}

TypeMismatch error in filling List from JSON

i am trying to fill a list from JSON in SwiftUI, which has this format:
{
"books": [{
"id": "87",
"title": "2001 odissea nello spazio",
"author_id": null,
"author": "arthur c. clarke",
"editor_id": null,
"editor": "longanesi",
"price": "0.00",
"isbn": "",
"note": ""
}, ......]
}
i created this struct for the Book object:
struct Book: Decodable, Identifiable {
public var id: Int;
public var title: String;
public var isbn: String;
enum CodingKeys: String, CodingKey {
case id = "id";
case title = "title";
case isbn = "isbn";
}
}
then i created this class to get the remote json:
import Foundation
public class GetBooks: ObservableObject {
#Published var books = [Book]();
init() {
load();
}
func load() {
let url = URL(string: "https://www.mattepuffo.com/api/book/get.php")!;
URLSession.shared.dataTask(with: url) {
(data, response, error) in
do {
if let d = data {
let decodedLists = JSONDecoder();
decodedLists.keyDecodingStrategy = .convertFromSnakeCase;
let dec = try decodedLists.decode([Book].self, from: d);
DispatchQueue.main.async {
self.books = dec;
}
} else {
print("Non ci sono libri");
}
} catch {
print(error)
}
}.resume();
}
}
but i get an error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
I think I understand what the problem is, but I don't understand how to solve it.
in the sense that the problem is that the json starts with an object (books) and not with an array.
but I don't understand how I have to modify the code!
I also tried to modify this line in this way, getting the error you see in the comment:
let dec = try decodedLists.decode(Book.self, from: d);
DispatchQueue.main.async {
self.books = dec; // Cannot assign value of type 'Book' to type '[Book]'
}
Your problem is that your JSON is not an Array of Book.
You need an upper level struct:
struct BookList: Decodable {
let books : [Book]
}
and then decode this structure instead of the array:
let dec = try decodedLists.decode(BookList.self, from: d);
DispatchQueue.main.async {
self.books = dec.books;
}
There are two major issues in your code:
You are ignoring the root object of the JSON, the dictionary with key books. This causes the error.
The type of key id is a string, in JSON everything in double quotes is String.
Further you don't need CodingKeys if all struct member names match the JSON keys and if the struct members are not going to be modified declare them as constants (let). Finally this is Swift: No trailing objective-c-ish semicolons.
struct Root: Decodable {
public let books: [Book]
}
struct Book: Decodable, Identifiable {
public let id: String
public let title: String
public let isbn: String
}
let result = try decodedLists.decode(Root.self, from: d)
DispatchQueue.main.async {
self.books = result.books
}

Error decoding JSON - keyNotFound(CodingKeys

When I try to decode this json:
"polls": [
{
"title": "title",
"date": "date",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
},
}
}, /*<and about 76 of this>*/ ]
with this Codable:
struct poll: Codable {
var stats: stats
var title: String?
var date: String?
var summary: String?
struct stats: Codable {
var total: total
struct total: Codable {
var nays: Int
var yays: Int
var nas: Int
var abstentions: Int
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I get the following error
keyNotFound(CodingKeys(stringValue: "dagegen gestimmt", intValue: nil)(if you need the full error text tell me)
I tried some of the answer from similar questions but nothing worked.
You apparently have occurrences of total where dagegen gestimmt is absent. So, make that an Optional, e.g. Int?:
struct Poll: Codable {
let stats: Stats
let title: String?
let date: Date?
let summary: String?
struct Stats: Codable {
let total: Total
struct Total: Codable {
let nays: Int?
let yays: Int?
let nas: Int?
let abstentions: Int?
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I’d also suggest the following, also reflected in the above:
Start type names (e.g. your struct names) with uppercase letter;
Use let instead of var as we should always favor immutability unless you really are going to be changing these values within this struct; and
If your date is in a consistent format, I’d suggest making the date a Date type, and then you can supply the JSONDecoder a dateDecodingStrategy that matches (see sample below).
For example:
let data = """
{
"polls": [
{
"title": "New Years Poll",
"date": "2019-01-01",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
}
}
},{
"title": "Caesar's Last Poll",
"date": "2019-03-15",
"summary": "summary2",
"stats": {
"total": {
"dafür gestimmt": 42
}
}
}
]
}
""".data(using: .utf8)!
struct Response: Codable {
let polls: [Poll]
}
do {
let decoderDateFormatter = DateFormatter()
decoderDateFormatter.dateFormat = "yyyy-MM-dd"
decoderDateFormatter.locale = Locale(identifier: "en_US_POSIX")
let userInterfaceDateFormatter = DateFormatter()
userInterfaceDateFormatter.dateStyle = .long
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(decoderDateFormatter)
let response = try decoder.decode(Response.self, from: data)
let polls = response.polls
for poll in polls {
print(poll.title ?? "No title")
print(" date:", poll.date.map { userInterfaceDateFormatter.string(from: $0) } ?? "No date supplied")
print(" yays:", poll.stats.total.yays ?? 0)
print(" nays:", poll.stats.total.nays ?? 0)
}
} catch {
print(error)
}
That produces:
New Years Poll
date: January 1, 2019
yays: 454
nays: 139
Caesar's Last Poll
date: March 15, 2019
yays: 42
nays: 0
Set your model as per following format. Also check datatype as per your response.
struct PollsModel:Codable{
var polls : [PollsArrayModel]
enum CodingKeys:String, CodingKey{
case polls
}
struct PollsArrayModel:Codable{
var title : String?
var date : String?
var summary : String?
var stats : PollsStatsModel
enum CodingKeys:String, CodingKey{
case title
case date
case summary
case stats
}
struct PollsStatsModel:Codable{
var total : PollsStatsTotalModel
enum CodingKeys:String, CodingKey{
case total
}
struct PollsStatsTotalModel:Codable{
var dagegen_gestimmt : Int?
var nicht_beteiligt : Int?
var dafür_gestimmt : Int?
var enthalten : Int?
enum CodingKeys:String, CodingKey{
case dagegen_gestimmt = "dagegen gestimmt"
case nicht_beteiligt = "nicht beteiligt"
case dafür_gestimmt = "dafür gestimmt"
case enthalten = "enthalten"
}
}
}
}
}

Swift 4 JSON Codable ids as keys

I would like to use Swift 4's codable feature with json but some of the keys do not have a set name. Rather there is an array and they are ids like
{
"status": "ok",
"messages": {
"generalMessages": [],
"recordMessages": []
},
"foundRows": 2515989,
"data": {
"181": {
"animalID": "181",
"animalName": "Sophie",
"animalBreed": "Domestic Short Hair / Domestic Short Hair / Mixed (short coat)",
"animalGeneralAge": "Adult",
"animalSex": "Female",
"animalPrimaryBreed": "Domestic Short Hair",
"animalUpdatedDate": "6/26/2015 2:00 PM",
"animalOrgID": "12",
"animalLocationDistance": ""
where you see the 181 ids. Does anyone know how to handle the 181 so I can specify it as a key? The number can be any number and is different for each one.
Would like something like this
struct messages: Codable {
var generalMessages: [String]
var recordMessages: [String]
}
struct data: Codable {
var
}
struct Cat: Codable {
var Status: String
var messages: messages
var foundRows: Int
//var 181: [data] //What do I place here
}
Thanks in advance.
Please check :
struct ResponseData: Codable {
struct Inner : Codable {
var animalID : String
var animalName : String
private enum CodingKeys : String, CodingKey {
case animalID = "animalID"
case animalName = "animalName"
}
}
var Status: String
var foundRows: Int
var data : [String: Inner]
private enum CodingKeys: String, CodingKey {
case Status = "status"
case foundRows = "foundRows"
case data = "data"
}
}
let json = """
{
"status": "ok",
"messages": {
"generalMessages": ["dsfsdf"],
"recordMessages": ["sdfsdf"]
},
"foundRows": 2515989,
"data": {
"181": {
"animalID": "181",
"animalName": "Sophie"
},
"182": {
"animalID": "182",
"animalName": "Sophie"
}
}
}
"""
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let jsonData = try decoder.decode(ResponseData.self, from: data)
for (key, value) in jsonData.data {
print(key)
print(value.animalID)
print(value.animalName)
}
}
catch {
print("error:\(error)")
}
I don't think you can declare a number as a variable name. From Apple's doc:
Constant and variable names can’t contain whitespace characters,
mathematical symbols, arrows, private-use (or invalid) Unicode code
points, or line- and box-drawing characters. Nor can they begin with a
number, although numbers may be included elsewhere within the name.
A proper way is to have a property capturing your key.
struct Cat: Codable {
var Status: String
var messages: messages
var foundRows: Int
var key: Int // your key, e.g., 181
}
I would suggest something like this:-
struct ResponseData: Codable {
struct AnimalData: Codable {
var animalId: String
var animalName: String
private enum CodingKeys: String, CodingKey {
case animalId = "animalID"
case animalName = "animalName"
}
}
var status: String
var foundRows: Int
var data: [AnimalData]
private enum CodingKeys: String, CodingKey {
case status = "status"
case foundRows = "foundRows"
case data = "data"
}
struct AnimalIdKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let animalsData = try container.nestedContainer(keyedBy: AnimalIdKey.self, forKey: .data)
self.foundRows = try container.decode(Int.self, forKey: .foundRows)
self.status = try container.decode(String.self, forKey: .status)
self.data = []
for key in animalsData.allKeys {
print(key)
let animalData = try animalsData.decode(AnimalData.self, forKey: key)
self.data.append(animalData)
}
}
}
let string = "{\"status\":\"ok\",\"messages\":{\"generalMessages\":[],\"recordMessages\":[]},\"foundRows\":2515989,\"data\":{\"181\":{\"animalID\":\"181\",\"animalName\":\"Sophie\"}}}"
let jsonData = string.data(using: .utf8)!
let decoder = JSONDecoder()
let parsedData = try? decoder.decode(ResponseData.self, from: jsonData)
This way your decoder initializer itself handles the keys.