I am trying to use specific capabilities not available in the available iOS SDK for Parse Platform (Server), but that I know are available with the REST API. Specifically to use a DISTINCT query.
Using the Parse Dashboard and REST API client app on my dev computer (Rested.app), I have verified the following query completes as expected:
curl -X GET \
-H "X-Parse-Application-Id: someAppID" \
-H "X-Parse-Master-Key: someKey" \
-G \
--data-urlencode "distinct=TeeTime" \
http://somehost:1337/parse/aggregate/CompEntry
Which successfully returns data:
{
"results": [
{
"__type": "Date",
"iso": "2020-08-29T07:00:00.000Z"
},
{
"__type": "Date",
"iso": "2020-08-29T07:09:00.000Z"
}
] }
The original data is from, which has 3 rows, 2 of which share the same TeeTime:
And a screenshot of the output from the Rested.app:
Now I am trying to convert this for my Swift / iOS project.
I am trying to move the downloaded data into a new struct to represent the object(s), using the Codable/Decodable approach and matching the JSON property names. The code I have so far is below (placed some comments inline too). The Struct definitions occur in separate .swift files, but so long as outside the main ViewController definition.
struct TeeTimeData: Codable {
let results: [Results]
}
struct Results: Codable {
let __type: String
let iso: String // TODO: THIS SHOULD BE A DIFFERENT DATA TYPE - I HAVE PICKED HARDER DATA TYPE TO START WITH!
}
Then within the main ViewController struct:
class ViewController: UIViewController {
#IBAction func buttonGetTeeTimes(_ sender: UIButton) {
if let url = URL(string: "http://somehost:1337/parse/aggregate/CompEntry") {
var request = URLRequest(url: url)
request.addValue("someAppID", forHTTPHeaderField: "X-Parse-Application-Id")
request.addValue("someKey", forHTTPHeaderField: "X-Parse-Master-Key")
request.httpMethod = "GET"
let params = ["distinct": "TeeTime"] // TODO: THIS VAR IS NOT ACTUALLY BEING TIED TO THE REQUEST EITHER - 2nd PROBLEM...
let session = URLSession(configuration: .default)
let requestTask = session.dataTask(with: request) { (data, response, error) in
if error != nil {
print("API Error: \(error)")
}
if let dataUnwrap = data {
// TODO: MOVE THIS INTO NEW CLASS (DataModel, etc)
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(TeeTimeData.self, from: dataUnwrap)
print(decodedData)
} catch {
print("Decode Error: \(error)")
}
}
}
requestTask.resume()
}
}
}
And the console output is:
Decode Error: keyNotFound(CodingKeys(stringValue: "__type", intValue:
nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"results", intValue: nil), _JSONKey(stringValue: "Index 0", intValue:
0)], debugDescription: "No value associated with key
CodingKeys(stringValue: "__type", intValue: nil) ("__type").",
underlyingError: nil))
My first guess is the 2 underscores, "__type", at the start of the property definition?
Answer provided by #Larme in the comments to the opening post. Contributions from #gcharita that my syntax should have worked if it was the same structure as the other tests I was performing (it wasn't - see below).
The error was occurring because the results being returned was a different JSON structure because the distinct=TeeTime filter wasn't being applied. So it was returning a JSON object with all the rows of data from the class, not those specified in my TeeTimeData struct object.
Appending ?distinct=TeeTime to the end of the URL string resolved the issue and returns:
Decoded data: TeeTimeData(results: [Parse_2.Results(__type: "Date",
iso: "2020-08-29T07:00:00.000Z"), Parse_2.Results(__type: "Date", iso:
"2020-08-29T07:09:00.000Z")])
It also means I no longer require the let params = ["distinct": "TeeTime"] code as well.
Related
I am making a POST request, and the API it's calling (hosted on AWS Lambda/Gateway) returns one int. Yet, when Swift tries to handle the API call, its errors out with:
typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))
Here is the task:
let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] data, _, error in
guard let data = data, error == nil else {
return
}
do {
let decoded = try JSONDecoder().decode(Int.self, from: data)
DispatchQueue.main.async {
self?.session_id = decoded
}
}
catch {
print("Error: something went wrong calling api posting session", error)
}
}
When I make the same call in Postman, the response is just:
119
I have tried changing the decode line to
.decode(String.self, from: data)
but then it fails again because of some thread issues.
I have also tried changing the back end to return an actual json object, as well as adding an object to swift, with one property being the desired session_id (with very carefully checked types, tried with Optionals and not), but it returns with error:
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))
The weirdest part is that whenever I make a GET request, swift has been decoding everything fine. Where could my issue be?
Hello I am creating an app with Xcode and I am having the following problem, I created this API (if you enter the link you'll see the JSON data) https://proyecto-idts6.epizy.com/models/getCategorias.php
If you dont want to enter the link here is how this si how the structure of the JSON looks like:
{
"items":[
{
"categorie":"Fruits",
"id_categorie":"1"
},
{
"categorie":"Animals",
"id_categorie":"2"
},
{
"categorie":"Juices",
"id_categorie":"3"
},
{
"categorie":"Vegetables",
"id_categorie":"4"
},
{
"categorie":"Alcohol",
"id_categorie":"5"
},
{
"categorie":"Desserts",
"id_categorie":"6"
}
]
}
The problem I have is that when I try to decode the data from the API it cant't be decoded properly, I am trying to recreate the same code of this youtube video, but with my API: https://www.youtube.com/watch?v=sqo844saoC4
What I want basically is to print the categories and storage each of them in variables (because i'll need to move the variables between screens)
This is how my code looks like:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://proyecto-idts6.epizy.com/models/getCategorias.php"
getData(from: url)
//Here is where i want to storage the variables from the JSON
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
do {
let result = try JSONDecoder().decode([ResultItem].self, from: data)
print(result)
}
catch {
print("failed to convert\(error)")
}
})
task.resume()
}
}
struct Response: Codable {
let items: [ResultItem]
}
struct ResultItem: Codable {
let categorie: String
}
My goal is to have variables for example like this: categorie1=("the category 1 called from the JSON"), categorie2=("the category 2 called from the JSON"), categorie3=("the category 3 called from the JSON"),...
The problem is not in the decoding but in the remote API.
Your endpoint (https://proyecto-idts6.epizy.com/models/getCategorias.php) instead of returning a JSON is returning the following HTML
<html><body><script type="text/javascript" src="/aes.js" ></script><script>function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e="",f=0;f<d.length;f++)e+=(16>d[f]?"0":"")+d[f].toString(16);return e.toLowerCase()}var a=toNumbers("f655ba9d09a112d4968c63579db590b4"),b=toNumbers("98344c2eee86c3994890592585b49f80"),c=toNumbers("f5490e280a5e50f74932909856c3d3a3");document.cookie="__test="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/"; location.href="https://proyecto-idts6.epizy.com/models/getCategorias.php?i=1";</script><noscript>This site requires Javascript to work, please enable Javascript in your browser or use a browser with Javascript support</noscript></body></html>
So you are trying to decode that HTML content, which clearly leads to the error your reported
failed to convertdataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around line 1, column 0." UserInfo={NSDebugDescription=Invalid value around line 1, column 0., NSJSONSerializationErrorIndex=0})))
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.
I created a nodejs lambda function in AWS and exposed it using APIGateway with methods GET, PUT, POST, and DELETE (all setup with proxy). All methods have been tested and work in AWS using APIGateway, and then outside of AWS using Postman.
First, I called the GET method for the endpoint in my Swift 4 project, and it is successful.
BUT I have tried just about everything to call the POST method in swift and cannot get it to execute successfully. This is what I am currently trying after researching online:
let awsEndpoint: String = "https://host/path"
guard let awsURL = URL(string: awsEndpoint) else {
print("Error: cannot create URL")
return
}
var postUrlRequest = URLRequest(url: awsURL)
postUrlRequest.httpMethod = "POST"
postUrlRequest.addValue("John Doe", forHTTPHeaderField: "name")
postUrlRequest.addValue("imageurl.com", forHTTPHeaderField: "imageUrl")
URLSession.shared.dataTask(with: postUrlRequest) { data, response, error in
guard let data = data else { return }
do {
guard let receivedTodo = try JSONSerialization.jsonObject(with: data,
options: []) as? [String: Any] else {
print("Error")
return
}
} catch let err{
print(err)
}
}.resume()
The response I get is ["message":"Internal Server Error"]. When I look at the logs in CloudWatch they are not very descriptive. The error log for the post call is:
"Execution failed due to configuration error: Malformed Lambda proxy response"
After researching this issue aws suggests to format the response in a specific way and I have updated my nodejs lambda function to mimmic this.
case "POST":
pool.getConnection(function(err, connection) {
const groupName = event.headers.name;
const imageUrl = event.headers.imageUrl;
var group = {Name: groupName, ImageUrl: imageUrl, IsActive:true, Created:date, Updated:date};
var query = "INSERT INTO Groups SET ?";
connection.query(query,group, function (error, results, fields) {
var responseBody = {
"key3": "value3",
"key2": "value2",
"key1": "value1"
};
var response = {
"statusCode": 200,
"headers": {
"my_header": "my_value"
},
"body": JSON.stringify(responseBody),
"isBase64Encoded": true
};
if (error) callback(error);
else callback(null, response)
connection.release();
});
});
break;
Like I said previously, this works when testing everywhere except swift 4. My GET call works with swift 4, so I do not think it is an issue with allowing anything in the info.plist but I could be wrong. I have tried just about everything, but cannot seem to get past this error.
I fixed this issue myself. After allowing ALL log output in API Gateway for that endpoint, I found that somewhere along the way my headers were being converted to all lowercase.
'imageUrl' became 'imageurl'
It was throwing an error because in my lambda function, it could not find 'imageUrl'
I think this is a conversion that is happening in APIGateway because I have never come across this issue with swift.
This is my first post, so I hope everything is as structured as it should be.
I hope anybody can help me to solve my issue.
I have the following issue while decoding JSON in Swift from a downloaded file:
The vocabulary.json file contains the following:
[
{
"english": "one",
"deutsch": "eins",
"theme": "numbers"
},
{
"english": "two",
"deutsch": "zwei",
"theme": "numbers"
}
]
JSON in file
My swift 4 - code:
public struct Vocabulary: Codable{
let english: String
let deutsch: String
let theme: String
}
func decodeData(){
DataManager.getJSONFromURL("vokabeln") { (data, error) in
guard let data = data else {
return
}
let decoder = JSONDecoder()
do {
let vocabulary = try decoder.decode(Vocabulary.self, from: data)
print(vocabulary)
} catch let e {
print("failed to convert data \(e)")
}
}
}
public final class DataManager {
public static func getJSONFromURL(_ resource:String, completion:#escaping (_ data:Data?, _ error:Error?) -> Void) {
DispatchQueue.global(qos: .background).async {
let url = URL(string: "https://onedrive.live.com/xxxxx/vokabeln.json")
let data = try! Data(contentsOf: url!, options: .uncached)
completion(data, nil)
}
}
}
If I decode the Json from the following multi string:
public let vokabeln: String = """
[
{
"english": "one",
"deutsch": "eins",
"theme": "numbers"
},
{
"english": "two",
"deutsch": "zwei",
"theme": "numbers"
}
]
"""
it works, but if I try to decode it from the file I receive the following error message:
failed to convert data dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.})))
Thank you in advance.
Kind regards,
Kai
Change this
let vocabulary = try decoder.decode(Vocabulary.self, from: data)
to this
let vocabulary = try decoder.decode([Vocabulary].self, from: data)
It will give an array of Vocabulary just like [Vocabulary].
I hope this will help you.
I was getting a very similar error:
caught: dataCorrupted(Swift.DecodingError.Context(codingPath: [],
debugDescription: "The given data was not valid JSON.",
underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840
"Badly formed object around character 64."
UserInfo={NSDebugDescription=Badly formed object around character
64.})))
But for a completely different reason:
My local json was created like this:
"""
{
"name": "Durian",
"rate": 600,
"city": "Cali"
"description": "A fruit with a distinctive scent."
}
"""
The error message is quite obvious. I had forgot to place , after "Cali".
If I understand this correctly, to count 64 characters you have to start counting from the beginning of the line where "name" is. Meaning there are 4 empty characters before in each line. Hence the number ~64. You don't need to count the spaces at the line of { :)
Placing the comma resolved the issue.