Everything is parsed correctly but one element always pulls null even though it's inside the Json? Swift 4.1 - ios

I am trying to parse the messages and it just pulls nil EVERY time. It would be fine if it was like once or twice, but it does it every time so something is. definitely going wrong here.
Here is what the console output is looking like
commitJson(sha: "3665294d1e813d35594d6bcdc0a61983caa6e0cd", message: nil, url: "https://api.github.com/repos/apple/swift/commits/3665294d1e813d35594d6bcdc0a61983caa6e0cd", commit: GitHubCommits.commit(author: GitHubCommits.author(date: Optional("2018-10-03T19:12:15Z"), name: "Karoy Lorentey")))
It is pulling everything but the message. I might be missing something, but I think it's better if I let my code talk. Sorry for the struct layout......
Here is the struct with the json
struct author : Codable{
var date: String
var name: String
}
struct commit : Codable {
var author: author
}
struct commitJson : Codable {
var sha: String
var message: String?
var url: String
var commit: commit
}
seems solid right? I need the optional or the thing will crash on me....
Here is the parsing
guard let url = URL(string: "https://api.github.com/repos/apple/swift/commits?per_page=100") else {return}
URLSession.shared.dataTask(with: url) { (data, statusCode, error) in
//print(statusCode)
if let error = error{
print("error : \(error)")
return
}
guard let data = data else {return}
do{
let decoder = JSONDecoder()
self.commitsArray = try decoder.decode([commitJson].self, from: data)
for commit in self.commitsArray{
print(commit)
}
} catch {
print("I have failed you with \(error)")
}
}.resume()
I feel like I am not doing anything wrong, but I wouldn't be here if I wasn't. I tried converting the thing into a string and switching the some stuff like the quotes then back into a data object, but I either got it wrong or it doesn't help at all.
Here is a cleaner sample to show what I want out of there.
*note this is all wrapped around an array brackets at the start and end
{
"sha": "80d765034c61d8bcad1d858cfa38ec599017a2f0",
"commit": {
"author": {
"name": "swift-ci",
"date": "2018-10-08T18:59:06Z"
}
"message": "Merge pull request #19764 from tokorom/vim-syntax-case-label-region",
}
Here is what a sample of what the GitHub full data block example looks like.
{
"sha": "80d765034c61d8bcad1d858cfa38ec599017a2f0",
"node_id": "MDY6Q29tbWl0NDQ4Mzg5NDk6ODBkNzY1MDM0YzYxZDhiY2FkMWQ4NThjZmEzOGVjNTk5MDE3YTJmMA==",
"commit": {
"author": {
"name": "swift-ci",
"email": "swift-ci#users.noreply.github.com",
"date": "2018-10-08T18:59:06Z"
},
"committer": {
"name": "GitHub",
"email": "noreply#github.com",
"date": "2018-10-08T18:59:06Z"
},
"message": "Merge pull request #19764 from tokorom/vim-syntax-case-label-region",
"tree": {
"sha": "d6bd4fe23f4efabcfee7fbfb6e91e5aac9b4bf6d",
"url": "https://api.github.com/repos/apple/swift/git/trees/d6bd4fe23f4efabcfee7fbfb6e91e5aac9b4bf6d"
},
"url": "https://api.github.com/repos/apple/swift/git/commits/80d765034c61d8bcad1d858cfa38ec599017a2f0",
"comment_count": 0,
"verification": {
"verified": true,
"reason": "valid",
"signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJbu6j6CRBK7hj4Ov3rIwAAdHIIAKv4lE8AwQ/hrqfjNaOdW/EW\nsFqNisjTOhj1YiW64VSU7l2uztogJJG0Shl/+zQQQGFNVcvxlNXjq3JF9rrThrPl\nFKwvNZoSZBgNoEbTNoMPCkS+GMVDlMw96VVHrSo4Nae4yiU+Y+WSnCqf6I+TUSRp\n5JyL6oMlSqaihgq9gkIqlDnp6i0lRJWtMyGJ7xUrJ0C985RyGyb6fG20/34UJ4TT\nzT/Beb0RyYOdwnXy+mOm/NnmhcVozOrBbZlR3X2e4myQJ6Q7INOOyYPpmAZxEXps\nmajg6J73cwaH2x6PxRmMJ3+qxCau+bX3v4pEEeT5nYEIH+hDK2uC2wC/PkM7VsU=\n=2jhi\n-----END PGP SIGNATURE-----\n",
"payload": "tree d6bd4fe23f4efabcfee7fbfb6e91e5aac9b4bf6d\nparent 52deae30eb5833e53ba68ebc8a9a87614630751d\nparent ea2c860ddb4817dc83c7152035aa05569f3a2770\nauthor swift-ci <swift-ci#users.noreply.github.com> 1539025146 -0700\ncommitter GitHub <noreply#github.com> 1539025146 -0700\n\nMerge pull request #19764 from tokorom/vim-syntax-case-label-region\n\n"
}
}
Here is the link to the API. It does have like a 60 requests per hour without an API Key limit, so be wary of that.
GitHub Json Swift

message is part of the commit, not part of the outer object.
You need:
struct author : Codable{
var date: String
var name: String
}
struct commit : Codable {
var author: author
var message: String?
}
struct commitJson : Codable {
var sha: String
var url: String
var commit: commit
}

Related

Populate model with API result

I am looking to populate my model with the "payload" section from my endpoint. I have created a model of DataResponse which has a record property of Payload. I would like to get only the data from the payload section of the API endpoint. My network call is incorrect and I must be structuring my models wrong, but I am not sure what needs to be fixed. I am not sure if it makes a difference but my endpoint was displaying as an XML and I converted it to JSON below.
struct DataResponse: Decodable {
let record: Payload
}
struct Payload: Decodable {
let SoldToday: Int
}
let url = URL(string: "https:------")!
URLSession.shared.dataTask(with: url) {data, response, error in
guard error == nil,
let data = data else {
print(error)
return
}
let dataResponse = try? JSONDecoder().decode(DataResponse.self, from: data)
if let dataResponse = dataResponse {
print(dataResponse.record.SoldToday)
}
}.resume()
These are the contents of my url endpoint:
{
"action": "API_DoQuery",
"errcode": "0",
"errtext": "No error",
"dbinfo": {
"name": "Daily",
"desc": []
},
"variables": {
"__iol": "&rand='+new Date().getTime())};\">",
"__script": "&rand='+new Date().getTime());void(0);",
"iol": "<img qbu='module' src='/i/clear2x2.gif' onload=\"javascript:if(typeof QBU=='undefined'){QBU={};$.getScript(gReqAppDBID+'?a=dbpage&pagename=",
"script": "javascript:$.getScript(gReqAppDBID+'?a=dbpage&pagename="
},
"chdbids": [],
"record": {
"payload": "{ \"RecordID\": 04-22-2022, \"SoldToday\": 18, \"ContractToday\": 869327, \"KWToday\": 160960 }",
"update_id": "1647544685640"
}
}
you need to fix 2 things to be able to decode your json data:
You need the models that match your json data. Such as:
struct DataResponse: Decodable {
let record: Record
}
struct Record: Decodable {
let payload: Payload
}
struct Payload: Decodable {
let SoldToday: Int
}
And you need to make sure your data is valid json. Currently variables is not valid, similarly for payload in record,
(it is enclosed in quotes). Once these are fixed, I was able to decode the data successfully in my tests.
Note that if your endpoint is giving you XML, then it is probably better to convert XML to your models directly, without having to convert to json. There are a number of XML parser libraries on github.

Is it possible to create Swift Codable for plain k-v json?

I've JSON data like:
{
"peopleA": "nnll",
"peopleB": "ihyt",
"peopleC": "udr",
"peopleD": "vhgd",
"peopleE": "llll"
}
There're thousands of data like that, basically what I wanna to do is read the JSON file, and fetch the relate info, like: input peopleC, return udr.
Trying to use some online solution, I got
struct Welcome: Codable {
let peopleA, peopleB, peopleC, peopleD: String
let peopleE: String
}
I know I can refactor the JSON file to:
{
"candidates": [
{
"name": "peopleA",
"info": "nnll"
},
{
"name": "peopleB",
"info": "ihyt"
},
{
"name": "peopleC",
"info": "udr"
}
]
}
And get the related Swift struct:
struct Welcome: Codable {
let candidates: [Candidate]
}
// MARK: - Candidate
struct Candidate: Codable {
let name, info: String
}
I'm just wondering if maybe we could make it work in Swift without postprocessing the json file?
You can simply decode it as a dictionary. Then you can map your dictionary into your array of Candidate structures if you would like to:
struct Welcome: Codable {
let candidates: [Candidate]
}
struct Candidate: Codable {
let name, info: String
}
let js = """
{
"peopleA": "nnll",
"peopleB": "ihyt",
"peopleC": "udr",
"peopleD": "vhgd",
"peopleE": "llll"
}
"""
do {
let dictionary = try JSONDecoder().decode([String: String].self, from: Data(js.utf8))
let welcome = Welcome(candidates: dictionary.map(Candidate.init))
print(welcome)
} catch {
print(error)
}
This will print:
Welcome(candidates: [Candidate(name: "peopleA", info: "nnll"), Candidate(name: "peopleC", info: "udr"), Candidate(name: "peopleB", info: "ihyt"), Candidate(name: "peopleE", info: "llll"), Candidate(name: "peopleD", info: "vhgd")])

How to handle failure cases of JSON response using Codable?

I have some JSON response, that I take from a server. In success case, it might be like:
{
"success": true,
"data": [
{
/// something here
}
]
}
If all server responses would be successful, it would be really easy to parse that JSON. But we have also failure cases like:
{
"success": false,
"msg": "Your session expired",
"end_session": true
}
That means we need to handle two cases. As you noticed, attributes like success, msg may occur in any response. In order to handle that, I created following struct:
struct RegularResponse<T: Codable>: Codable {
let success: Bool
let msg: String?
let endSession: Bool?
let data: T?
enum CodingKeys: String, CodingKey {
case success, msg, data
case endSession = "end_session"
}
}
It may contain some data if response is successfull or otherwise, it is possible to identify why the error occurred(using success attribute or msg). Parsing process would go like following:
let model = try JSONDecoder().decode(RegularResponse<MyModel>.self, from: data)
if model.success {
// do something with data
} else {
// handle error
}
Everything works fine, but what if following JSON comes as following:
{
"success": true,
"name": "Jon Snow",
"living_place": "Nights Watch",
//some other fields
}
Here, I don't have data attribute. It means, my RegularResponse cannot be parsed. So, the question is how to handle these kind of situations? My idea for solution is simple: always put data in success cases into data field on my API. By doing so, my RegularResponse will always work, no matter what is inside data. But, it requires changes on a server side. Can this be fixed in a client side, not changing a server side? In other words, how to handle above situation in Swift using Codable?
I'm not sure if this is the best solution but if you know that your error response is in that shape, i.e.:
{
"success": false,
"msg": "Some error",
"end_session": "true",
}
then you could make another Codable struct/class that follows this response.
struct ErrorResponse: Codable {
let success: Bool
let msg: String
let end_session: String
}
and then when you are responding to your JSON you could adjust your code to:
if let successResponse = try? JSONDecoder().decode(RegularResponse<MyModel>.self, from: data) {
//handle success
} else if let responseError = try? JSONDecoder().decode(ErrorResponse.self, from data) {
//handle your error
}

Vapor 3: transform array of Future object to an array of Future other objects

I tried to make the most basic example that I could think of for my problem. I have a Course model and a many-to-many table to User that also stores some extra properties (the progress in the example below).
import FluentPostgreSQL
import Vapor
final class Course: Codable, PostgreSQLModel {
var id: Int?
var name: String
var teacherId: User.ID
var teacher: Parent<Course, User> {
return parent(\.teacherId)
}
init(name: String, teacherId: User.ID) {
self.name = name
self.teacherId = teacherId
}
}
struct CourseUser: Pivot, PostgreSQLModel {
typealias Left = Course
typealias Right = User
static var leftIDKey: LeftIDKey = \.courseID
static var rightIDKey: RightIDKey = \.userID
var id: Int?
var courseID: Int
var userID: UUID
var progress: Int
var user: Parent<CourseUser, User> {
return parent(\.userID)
}
}
Now, when I return a Course object, I want the JSON output to be something like this:
{
"id": 1,
"name": "Course 1",
"teacher": {"name": "Mr. Teacher"},
"students": [
{"user": {"name": "Student 1"}, progress: 10},
{"user": {"name": "Student 2"}, progress: 60},
]
}
Instead of what I would normally get, which is this:
{
"id": 1,
"name": "Course 1",
"teacherID": 1,
}
So I created some extra models and a function to translate between them:
struct PublicCourseData: Content {
var id: Int?
let name: String
let teacher: User
let students: [Student]?
}
struct Student: Content {
let user: User
let progress: Int
}
extension Course {
func convertToPublicCourseData(req: Request) throws -> Future<PublicCourseData> {
let teacherQuery = self.teacher.get(on: req)
let studentsQuery = try CourseUser.query(on: req).filter(\.courseID == self.requireID()).all()
return map(to: PublicCourseData.self, teacherQuery, studentsQuery) { (teacher, students) in
return try PublicCourseData(id: self.requireID(),
name: self.name,
teacher: teacher,
students: nil) // <- students is the wrong type!
}
}
}
Now, I am almost there, but I am not able to convert studentsQuery from EventLoopFuture<[CourseUser]> to EventLoopFuture<[Student]>. I tried multiple combinations of map and flatMap, but I can't figure out how to translate an array of Futures to an array of different Futures.
The logic you're looking for will look like this
extension Course {
func convertToPublicCourseData(req: Request) throws -> Future<PublicCourseData> {
return teacher.get(on: req).flatMap { teacher in
return try CourseUser.query(on: req)
.filter(\.courseID == self.requireID())
.all().flatMap { courseUsers in
// here we should query a user for each courseUser
// and only then convert all of them into PublicCourseData
// but it will execute a lot of queries and it's not a good idea
}
}
}
}
I suggest you to use the SwifQL lib instead to build a custom query to get needed data in one request 🙂
You could mix Fluent's queries with SwifQL's in case if you want to get only one course, so you'll get it in 2 requests:
struct Student: Content {
let name: String
let progress: Int
}
extension Course {
func convertToPublicCourseData(req: Request) throws -> Future<PublicCourseData> {
return teacher.get(on: req).flatMap { teacher in
// we could use SwifQL here to query students in one request
return SwifQL.select(\CourseUser.progress, \User.name)
.from(CourseUser.table)
.join(.inner, User.table, on: \CourseUser.userID == \User.id)
.execute(on: req, as: .psql)
.all(decoding: Student.self).map { students in
return try PublicCourseData(id: self.requireID(),
name: self.name,
teacher: teacher,
students: students)
}
}
}
}
If you want to get a list of courses in one request you could use pure SwifQL query.
I simplified desired JSON a little bit
{
"id": 1,
"name": "Course 1",
"teacher": {"name": "Mr. Teacher"},
"students": [
{"name": "Student 1", progress: 10},
{"name": "Student 2", progress: 60},
]
}
first of all let's create a model to be able to decode query result into it
struct CoursePublic: Content {
let id: Int
let name: String
struct Teacher:: Codable {
let name: String
}
let teacher: Teacher
struct Student:: Codable {
let name: String
let progress: Int
}
let students: [Student]
}
Ok now we are ready to build a custom query. Let's build it in some request handler function
func getCourses(_ req: Request) throws -> Future<[CoursePublic]> {
/// create an alias for student
let s = User.as("student")
/// build a PostgreSQL's json object for student
let studentObject = PgJsonObject()
.field(key: "name", value: s~\.name)
.field(key: "progress", value: \CourseUser.progress)
/// Build students subquery
let studentsSubQuery = SwifQL
.select(Fn.coalesce(Fn.jsonb_agg(studentObject),
PgArray(emptyMode: .dollar) => .jsonb))
.from(s.table)
.where(s~\.id == \CourseUser.userID)
/// Finally build the whole query
let query = SwifQLSelectBuilder()
.select(\Course.id, \Course.name)
.select(Fn.to_jsonb(User.table) => "teacher")
.select(|studentsSubQuery| => "students")
.from(User.table)
.join(.inner, User.table, on: \Course.teacherId == \User.id)
.join(.leftOuter, CourseUser.table, on: \CourseUser.teacherId == \User.id)
.build()
/// this way you could print raw query
/// to execute it in postgres manually
/// for debugging purposes (e.g. in Postico app)
print("raw query: " + query.prepare(.psql).plain)
/// executes query with postgres dialect
return query.execute(on: req, as: .psql)
/// requests an array of results (or use .first if you need only one first row)
/// You also could decode query results into the custom struct
.all(decoding: CoursePublic.self)
}
Hope it will help you. There may be some mistakes in the query cause I wrote it without checking 🙂 You can try to print a raw query to copy it and execute in e.g. Postico app in postgres directly to understand what's wrong.

Xcode suddenly quit in runtime when parsing JSON

I'm trying to parse JSON with struct :
this is the JSON looks alike (the object value on SEAT has more than 100):
{
"Message": "Success",
"Status": 200,
"data": {
"SEATS": [
{
"SEAT_LOC_NO": "01404301",
"ROW_NM": "A",
"SEAT_NO": 1
},
{
"SEAT_LOC_NO": "01404401",
"ROW_NM": "A",
"SEAT_NO": 2
}
],
"SEATCOUNT": {
"COL_CNT": 42,
"ROW_CNT": 12,
}
}
}
and my struct be like:
struct Response : Codable {
var data : datas?
var Message : String?
var Status : Int64?
}
struct datas : Codable {
var SEATS : [SEATS]?
var SEATCOUNT : SEATINFO?
}
struct SEATS : Codable {
var SEAT_LOC_NO : String?
var ROW_NM : String?
var SEAT_NO : String?
}
struct SEATINFO : Codable {
var COL_CNT : Int64?
var ROW_CNT : Int64?
}
and this is the process of parsing:
var getSeat = Response()
getSeat = try? JSONDecoder().decode(Response.self, from: json) as Response
The xcode exit when trying to run this progress, json is a Data type which has been called in API Service.
I've been trying to delete derived data, remove some data in xcodeproject, restarting xcode and OS. However this problem still comes. And I wonder why but only on this process xcode suddenly quit while the others process is fine (different data to parse).
I'm using the latest XCODE Version.
Is it a bug, failed parsing or something else?
Your "SEAT_NO" key is integer value. You declare it as String? in struct.
Change var SEAT_NO : String? to var SEAT_NO : Int? or value in "SEAT_NO" key to String
If there is more problems try to catch errors
do {
getSeat = try JSONDecoder().decode(Response.self, from: data)
} catch {
print(error)
}
#Vanillatte please check your JSON first it's not valid please remove extra semicolon seat count dictionary. and try and to use error handling during parsing response.

Resources