Parse JSON causes swift dynamic cast failed - php

The goal is to display a result of json_encode result from php into swift as a label. I have referenced to many tutorials and practices of parsing json with swift, but I still cant understand the issue.
I referenced to an example that uses date.jsontest.com and that works fine.
In my project, I have parsed this simple JSON object to start:
[{"username":"user"}]
Here is the swift code:
override func viewDidLoad() {
super.viewDidLoad()
// 1
//let urlAsString = "http://date.jsontest.com"
let urlAsString = "http://(ip address)/service.php"
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
// 2
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
if (error != nil) {
println(error.localizedDescription)
}
var err: NSError?
// 3
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if (err != nil) {
println("JSON Error \(err!.localizedDescription)")
}
// 4
//let jsonDate: String! = jsonResult["date"] as NSString
//let jsonId = jsonResult["id"] as NSString
//let jsonTime: String! = jsonResult["time"] as NSString
let jsonUser: String! = jsonResult[0] as NSString
dispatch_async(dispatch_get_main_queue(), {
//self.dateLabel.text = jsonId
//self.timeLabel.text = jsonUser
self.dateLabel.text = jsonUser
})
})
// 5
jsonQuery.resume()
//searchItunesFor("JQ Software")
//startConnection()
// Do any additional setup after loading the view, typically from a nib.
}
The code runs fine when I use http://date.jsontest.com as the string along with jsonDate and jsonTime.
I tried to understand the debugger in Xcode, but as a new user to Swift, its hard to understand what the issue is. All I could root out is that there is an issue with how my JSON is formatted.
Any tips are greatly appreciated, thanks.
EDIT:
Here is the code http://(ip address)/service.php that gives me the JSON
<?php
//connect to MySQL
// Create connection
$con=mysqli_connect($hostname,$username,$password,$database);
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// This SQL statement selects ALL from the table 'Locations'
$sql = "SELECT username FROM members WHERE id = 1";
// Check if there are results
if ($result = mysqli_query($con, $sql))
{
// If so, then create a results array and a temporary one
// to hold the data
$resultArray = array();
$tempArray = array();
// Loop through each row in the result set
while($row = $result->fetch_object())
{
// Add each row into our results array
$tempArray = $row;
array_push($resultArray, $tempArray);
}
// Finally, encode the array to JSON and output the results
echo json_encode($resultArray);
}
// Close connections
mysqli_close($con);
?>

Related

Retrieved JSON data not decoding to custom struct in xcode 12

I am currently in the process of learning how to retrieve encoded JSON data from a mySQL database on an apache server and decode the JSON into an instance of my own custom struct;
struct Person: Codable, FetchableRecord, MutablePersistableRecord {
var id: Int64
var firstName: String
var lastName: String
}
here is my method for the network request on xcode
func dataRequestDownload() {
let baseURL = URL(string: "http://example.com/db_request.php?action=request")
DispatchQueue.main.async {
if let url = baseURL {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let decoder = JSONDecoder()
let person = try? decoder.decode(Person.self, from: data)
print(person)
}
}
task.resume()
}
}
}
}
My issue is that person prints as nil which makes me think the data isnt being decoded properly.
This is my PHP script for the GET request.
<?php
$con=mysqli_connect("127.0.0.1","root","example_password","example_database");
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$sql = "SELECT * FROM person";
if ($result = mysqli_query($con, $sql))
{
// If so, then create a results array and a temporary one
// to hold the data
$resultArray = array();
$tempArray = array();
// Loop through each row in the result set
while($row = $result->fetch_object())
{
// Add each row into our results array
$tempArray = $row;
array_push($resultArray, $tempArray);
}
// Finally, encode the array to JSON and output the results
echo json_encode($resultArray);
}
// Close connections
mysqli_close($con)
?>
Finally I am not sure this info matters, but in the mySQL database the table is set up just like my struct where id is the PRIMARY key. If more information is needed let me know in the comments, but as far as I know this is all that seems connected to my issue.
Edit: Some other possibly important information is when calling print(data) and print(response) I get
48 bytes
{ Status Code: 200, Headers {
Connection = (
"Keep-Alive"
);
"Content-Length" = (
48
);
"Content-Type" = (
"text/html; charset=UTF-8"
);
Date = (
"Thu, 17 Jun 2021 18:43:02 GMT"
);
"Keep-Alive" = (
"timeout=5, max=100"
);
Server = (
"Apache/2.4.46 (Win64) PHP/7.3.21"
);
"X-Powered-By" = (
"PHP/7.3.21"
);
} })
the URL is exempt from this of course.
Per the request in the comments; having done this
let object = try? JSONSerialization.jsonObject(with: data)
print(object)
I get
Optional(<__NSSingleObjectArrayI 0x281510080>(
{
firstName = John;
id = 0;
lastName = Doe;
}
)
)
Edit2: Upon running
do {
let person = try decoder.decode([Person].self, from: data)
print(person)
} catch {
print(error)
}
the following error appears
typeMismatch(Swift.Int64, Swift.DecodingError.Context(codingPath [_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "id", intValue: nil)], debugDescription:
"Expected to decode Int64 but found a string/data instead.", underlyingError: nil))
Here is the actual JSON upon visiting the URL
[{"id":"0","firstName":"John","lastName":"Doe"}]
When decoding JSON, it's helpful to use do/try/catch so that you can actually see what the error is.
It looks like, in this case, you have an array (which is clear from your PHP code), but you're trying to decode a single object. Try this:
do {
let person = try decoder.decode([Person].self, from: data)
print(person)
} catch {
print(error)
}
Then, since you'll have an array in person (which might be more accurately named people at this point), you'll want to access it with something like person.first
Update, based on added code in your edits:
That error is telling you that id is a String in the JSON. Either adjust your Person model to use String for the type of id instead of Int64, or adjust your database and or PHP code to use a number instead of a String for the id.

Incomplete Base64 when sending to server

I am trying to send an image to server, but the image should be in Base64 format. I am using this function to send it:
func upload_signature_staff(ticket: NSString){
let defaults = NSUserDefaults.standardUserDefaults()
let stringOne = defaults.stringForKey(defaultsKeys.staff_id)
let stringtwo = defaults.stringForKey(defaultsKeys.mst_customer)
let sig = defaults.stringForKey("staff_signature")
let request = NSMutableURLRequest(URL: NSURL(string: "http://xxxxxxxxxxxxx/upload.php")!)
request.HTTPMethod = "POST"
let postString = "action=add_signature&mst_customer=\((stringtwo!))&ticket=\((ticket))&signature=\((sig!))&current_user=\((stringOne!))&item_type=10"
print(postString)
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString)")
}
task.resume()
//self.performSegueWithIdentifier("goto_main2", sender: self)
}
The sig variable holds the Base64 string, it is being printed in my console so I can verify that the string is correct. I am also printing the postString and upon inspection it is also correct that the signature is matching the Base64 String. But when I open phpmyadmin, I see the field of my image with incomplete Base64 string, maybe 1/4 is just there.
Here's my php code, in case you want to see it:
<?php
require_once("../c.php");
$action = trim($_POST['action']);
if($action == "add_signature"){
$mst_customer = trim($_POST['mst_customer']);
$ticket = trim($_POST['ticket']);
$signature = trim($_POST['signature']);
$current_user = trim($_POST['current_user']);
$item_type = trim($_POST['item_type']);
$inputtime = time();
$sql = "INSERT INTO ticket_items SET mst_customer = '$mst_customer', ticket = '$ticket', ticket_item_type = '$item_type', details = '$signature', addedby = '$current_user', lastupdate = '$inputtime' ";
mysql_query($sql) or die(mysql_error());
}
?>
I think this was solved in the comments but here's a recap:
Inserting base64 strings that were too long (variable length?) in a varchar(255) field resulted in missing data. As far as I can tell, increasing the size of the field solved the immediate problem. I use the term "immediate" because, as #YvesLeBorg pointed out in the comments, this is bound to fail at some point without input size restrictions on the backend.
Additionally, I couldn't ignore the fact that the PHP/SQL code was wide open to injections.
Passing $mst_customer = trim($_POST['mst_customer']); on to "INSERT INTO ticket_items SET mst_customer = '$mst_customer' and then executing via mysql_query($sql) or die(mysql_error()); is dangerous!
Anybody could write anything in the $_POST parameter and SQL would happily accept it. Prepared statements, PDO, input sanitization etc. are there for a reason.
Finally, there was an issue concerning vanishing + signs in the base64 data. This was the result of missing url encoding of the post data.
I think that sums it up.

How to upload a UIImage to MySQL via PHP from Swift?

I have an app that uploads some data to a MySQL database via some PHP web services I wrote. I have got the text data to upload successfully, but I am struggling trying to get an image to upload. Basically, the user takes a photo using their iPhone camera, I convert it to a UIImage and now want to upload it.
I have looked at the following sources for help but they haven't seemed to work for me:
How to Upload text, pdf, doc, zip file via PHP file upload to MySQL
https://www.youtube.com/watch?v=HqxeyS961Uk (The code in here wouldn't compile for me and I copied it letter for letter)
How to post a picture from iPhone app (in Swift) to MySQL database?
Saving image on db mysql through swift
Swift - uploading base64 encoded image to php and displaying the image
Getting a UIImage from MySQL using PHP and jSON
I want to upload the image and text together in the same query if possible, as they relate to one another and my MySQL table assigns an auto number for new rows.
Edit
Thanks to the answers below. I am a little unsure as to how to implement the changes you guys suggested. My Swift code at the moment to upload text data is:
let url:NSURL = NSURL(string: "http://www.website.com.au/savenewexpense.php")!
let request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
var dataDictionary:[String:AnyObject] = ["Date" : strDate, "Staff" : strStaff, "Amount" : dblAmount, "Category" : strCategory]
var data:NSData = NSJSONSerialization.dataWithJSONObject(dataDictionary, options: nil, error: nil)!
request.HTTPBody = data
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data:NSData!, response:NSURLResponse!, error:NSError!) -> Void in
let response:[String:String] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! [String:String]
if let result = response["result"] {
if result == "success" {
NSLog("Text Data saved successfully")
if self.SendImageToServer() == true {
NSLog("Image Data saved successfully")
self.SaveSuccessful()
} else {
NSLog("Image Data failed to save")
self.DataFail()
}
} else {
NSLog("Error saving data")
self.DataFail()
}
} else {
NSLog("No response")
self.NoResponse()
}
})
task.resume()
}
and my PHP code is:
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$json = file_get_contents('php://input');
$data = json_decode($json, true);
$stmt = $conn->prepare("INSERT INTO tblExpenses (Date, Staff, Amount, Category) VALUES (?, ?, ?, ?)");
$txtDate = $data['Date'];
$txtStaff = $data['Staff'];
$txtAmount = $data['Amount'];
$txtCategory = $data['Category'];
$stmt->bind_param("ssds", $txtDate, $txtStaff, $txtAmount, $txtCategory);
$stmt->execute();
echo '{"result" : "success"}';
$stmt->close();
$conn->close();
How should I implement the image into this?
Thanks
What you have to do is set the Boundary for the Image as an example use the code to Convert the NSDATA of image to the boundary values
if let dta = data as? NSData {
bodyData.appendData(String(format:"\r\n--\(boundary)\r\n").dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!);
bodyData.appendData(String(format:"Content-Disposition: form-data; name=\"photo\(i)\"; filename=\"image.png\"\r\n").dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!);
bodyData.appendData(String(format:"Content-Type: image/png\r\n\r\n").dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!);
bodyData.appendData(dta);
}
Here what you do is you set the image name and then the Image Data first you convert the data using allowLossyConversion and then send the POST data with the other data to service
request.HTTPBody = bodyData
just use this to send the image

How to handle PHP errors in Swift

I'm using PHP & JSON to extract some data from a database.
This is my PHP file
<?php
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
echo "Failed to connect to DB.";
die();
} else {
$dbs = array();
$res = $mysqli->query("SHOW DATABASES");
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
echo json_encode($dbs);
} else {
echo "Failed to get list of databases from server.";
die();
}} ?>
If the password is wrong, then the system outputs "Failed to connect to DB"
In my program, I have things to handle errors, but I am stuck at one part.
let urlString = "http://\(hostTextField.text):\(portTextField.text)/dblist.php? &password=\(passTextField.text)"
let url: NSURL = NSURL(string: urlString)!
let urlSession = NSURLSession.sharedSession()
println(url)
println(urlSession)
//2
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
println(response)
println(data)
if (error != nil) {
println("Can't connect using credentials")
dispatch_async(dispatch_get_main_queue(), {
HUDController.sharedController.hide(afterDelay: 0.1)
})
sleep(1)
var refreshAlert = UIAlertController(title: "Camaleon Reports", message: "Can't connect to the database", preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction(UIAlertAction(title: "Retry", style: .Default, handler: { (action: UIAlertAction!) in
println("Yes Logic")
}))
self.presentViewController(refreshAlert, animated: true, completion: nil)
return }
var err: NSError?
var jsonResult: [Dictionary<String, String>] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as [Dictionary<String, String>]
// 3
if (err != nil) {
println("Still cant connect....")
println("JSON Error \(err!.localizedDescription)")
}
var jsonDB : [Dictionary<String, String>] = jsonResult
for currentDictionary in jsonDB{
var currentEntry = currentDictionary["Database"] as String!
My program crashes if I don't have the right password, but have the right IP address and Port/User for the MYSQL Database.
It crashes with this:
fatal error: unexpectedly found nil while unwrapping an Optional value
and points towards jsonResult. It makes sense, cause I don't retrieve two strings.
My problem is that if my password is off, then my PHP file echoes a string. How can I search for that string so that I can use an if statement and stop my application from crashing?
Your problem is likely in this line (wrapped for clarity):
var jsonResult: [Dictionary<String, String>] =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]
When your PHP script reports the error by just returning the string, it has returned invalid JSON. When you use NSJSONSerialization.JSONObjectWithData to parse it, that method will return nil if the JSON is invalid, as yours is.
You then take that value and assign it to a Swift variable that you've declared is not an optional. Trying to assign nil to a variable not declared with either ? or ! is a runtime error in Swift. (You don't get an error at compile time because you're using as to cast the value.)
One way to fix this would be to change your PHP so the error is proper JSON:
echo "{ \"error\": \"Failed to connect to DB.\" }"; # or something, my PHP is rusty
But that still leaves your Swift program in a fragile state; getting anything but proper JSON back from the server will make it crash.
Better is to declare the jsonResult variable as being an optional:
var jsonResult: [Dictionary<String, String>]? =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]?
Then in your code you can explicitly check whether jsonResult is nil, and if it is, you know an error has occurred, and can go back and look at the data object to see what it was.
Even that, though, can leave you in trouble. The root of a JSON document doesn't have to be a dictionary; it could be an array. And even if it is a dictionary, the values may not all be strings; they could be numbers, booleans, or nested arrays or dictionaries!
Objective-C's relatively lax type checking makes this easy to deal with, but Swift is stricter. Best might be to use one of the Swift-specific JSON libraries. That'll make your code far more robust.
Good luck!
There are two issues. One is the PHP and one is the Swift.
Your PHP really should never just report an error message. I'd suggest that it always return JSON. This will make it easier for your client code to detect and handle errors appropriately.
<?php
header("Content-Type: application/json");
$response = array();
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
if (!isset($_REQUEST['password'])) {
$response["success"] = false;
$response["error_code"] = 1;
$response["error_message"] = "No password provided";
echo json_encode($response);
exit();
}
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
$response["success"] = false;
$response["error_code"] = 2;
$response["mysql_error_code"] = $mysqli->connect_errno;
$response["error_message"] = $mysqli->connect_error;
echo json_encode($response);
exit();
}
if ($res = $mysqli->query("SHOW DATABASES")) {
$dbs = array();
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
$response["success"] = true;
$response["results"] = $dbs;
} else {
$response["success"] = false;
$response["error_code"] = 3;
$response["error_message"] = "Failed to get list of databases from server.";
}
$res->close();
} else {
$response["success"] = false;
$response["error_code"] = 4;
$response["mysql_error_code"] = $mysqli->errno;
$response["error_message"] = $mysqli->error;
}
$mysqli->close();
echo json_encode($response);
?>
Note, this:
Specifies application/json header for Content-Type;
Always returns a dictionary, containing
a "success" key, which is either true or false;
if an error, an error_code indicating the type of error (1 = no password provided; 2 = connect failed; 3 = no databases found; 4 = some SQL error);
if an error, an error_msg string indicating the error message string; and
if a success, a results array (much like you used to return at the root level).
On the Swift side, you need to :
Change it to look for these various server app-level errors (note, I make the top level structure a dictionary, and your original array of dictionaries a particular value;
You might want to proactively check the statusCode of the response object, to make sure the server gave you a 200 return code (e.g. 404 means that the page was not found, etc.);
You might also want to check for JSON parsing errors (in case some bug in the server prevented well-formed JSON from being returned); and
You really should be percent-escaping the password (because if it included + or & characters, it wouldn't get transmitted successfully otherwise).
Thus, you might have something like:
let encodedPassword = password.stringByAddingPercentEncodingForURLQueryValue()!
let body = "password=\(encodedPassword)"
let request = NSMutableURLRequest(URL: URL!)
request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)!
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// detect fundamental network error
guard error == nil && data != nil else {
print("network error: \(error)")
return
}
// detect fundamental server errors
if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode != 200 {
// some server error
print("status code was \(httpResponse.statusCode); not 200")
return
}
// detect parsing errors
guard let responseObject = try? NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String : AnyObject] else {
// some problem parsing the JSON response
print(String(data: data!, encoding: NSUTF8StringEncoding))
return
}
// now parse app-level response to make sure `status` was `true`
guard let success = responseObject!["success"] as? NSNumber else {
print("Problem extracting the `success` value") // we should never get here
return
}
if !success.boolValue {
print("server reported error")
if let errorCode = responseObject!["error_code"] as? NSNumber {
switch (errorCode.integerValue) {
case 1:
print("No password provided")
case 2:
print("Connection failed; probably bad password")
case 3:
print("No databases found")
case 4:
print("Some SQL error")
default:
print("Unknown error code: \(errorCode)") // should never get here
}
}
if let errorMessage = responseObject!["error_message"] as? String {
print(" message=\(errorMessage)")
}
return
}
if let databases = responseObject!["results"] as? [[String : AnyObject]] {
print("databases = \(databases)")
}
}
task.resume()
The percent-escaping code is in a String category:
extension String {
// see RFC 3986
func stringByAddingPercentEncodingForURLQueryValue() -> String? {
let characterSet = NSCharacterSet(charactersInString:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
return self.stringByAddingPercentEncodingWithAllowedCharacters(characterSet)
}
}
A couple of other ancillary observations:
Never send a passwords in the clear. Put them in the body of a POST request (not the URL), and then use a https URL.
I'd personally not use the MySQL password part of the app-level authentication. I'd keep MySQL authentication logic encoded on the server-side, and then use your own user authentication table.

Pass MySQL medium blob into IOS NSData\UIImage

In my PHP, I'm tying to send an array with data from the database. All values should be accepted as NSString exept one image that is stored as 'medium blob' in the database, and I'm trying to pass it's NSData\UIImage value.
while ($row = mysql_fetch_array($result)) {
$add = array();
$add["Id"] = $row["Id"];
$add["Mail"] = $row["Mail"];
$add["Category"] = $row["Category"];
$add["Phone"] = $row["Phone"];
$add["Msgs"] = $row["Msgs"];
//image from blob
$add["Picture"] = $row["Picture"];
// push single add into final response array
array_push($response["adds"], $add);
}
// success
$response["success"] = 1;
$response["message"] = "Adds loaded successfully";
// echoing JSON response
echo json_encode($response);
Now in Xcode, all values are received just fine except the image that is NULL. I tried to encode it with base64 in PHP and decode in Objective-C, but xcode collapses because it's still NULL. I tried to define:
header("Content-type: image/png");
or
file_put_contents("image.png", $add["Picture"]);
but it's always received as NULL. Rows that don't have blobs stored in them return as ' ', only the one with content returns NULL. I'm new to PHP, and I'm sure I'm doing some basic stupid mistake.
BTW my Objective-C code is:
const void *bytes = [[addPicture objectAtIndex:indexPath.row] bytes];
int length = [[addPicture objectAtIndex:indexPath.row] length];
NSData* imageData = [[NSData alloc] initWithBytes:bytes length:length];
aCell.cellBackImg.image = [UIImage imageWithData:imageData];

Categories