Error "Garbage at end" when serializing valid JSON data with Alamofire - json

My code return a Code=3840 "Garbage at end." when I try to keep my data of a request to my api ... The JSON return is a Valid JSON accorded to jsonlint (tested with Postman):
{
"error": 0,
"message": "transaction_completed"
}
this is my code :
func request(urle : url, parameters : Parameters, completion: #escaping (JSON) -> Void)
{
Alamofire.request(getUrl(urlw: urle), method: .post, parameters: parameters).responseJSON
{
response in
if response.data != nil {
do{
let json = try JSON(data: response.data!)
completion(json)
}catch{
print(error)
}
}
}
}
and this is when I called the request function:
let parameters: Parameters=[
"key" : user.key,
"uid": user.id
]
api.request(urle: .buyStack, parameters: parameters) { json in
print(json)
}
Where did I go wrong?

So apparently your JSON is not valid, it has at the end some invalid values.
First thing to do. For the sake of the keeping the logic, you can use force unwrap (using !) because it's debugging. I'm not sure that this code compile, it's just a logic presentation.
let responseString = String(data: response.data, encoding: .utf8)
print("responseString: \(responseString)")
This gives:
{"error":1,"message":"Undefined APIKey"}[]
There is extra [] at the end, and it's then not a valid JSON. You can ask the developper to fix it. If you really can't, or want to continue developing while it's in progress in their side, you can remove the extra [].
You can check this answer to remove the last two characters, and then:
let cleanResponseJSONString = //check the linked answer
let cleanResponseData = cleanResponseJSONString.data(encoding: .utf8)
let json = try JSON(data: cleanResponseData)
Side note and debugging idea if it was more complicate:
I ask for print("data: \(response.data as! NSData)") because this print the hex data. Your issue could have been due to an invisible character at the end. If you don't know them, the least you can do is according to previous answer:
let jsonString = "{\"error\":1,\"message\":\"Undefined APIKey\"}" (that's almost reponseString)
let jsonData = jsonString.data(encoding: .utf8)
print("jsonData: \(jsonData as! NSData)")
And compare what the end looks like.
A debugger tip, you can use a answer like this one to convert hexDataString into Data and debug from it. I'd recommend to add a space, "<" and ">" removal before so you can easily copy/paste it from the debugger output.
Why? If it's long (many manipulation) to go where your issue lies (login in the app, certain actions to do etc.), this could save you time to debug it on another app (Playground, at the start of your AppDelegate, etc.).
Don't forget to remove all the debug code afterwards ;)
Not related to the issue:
if response.data != nil {
do {
let json = try JSON(data: response.data!)
...
} catch {
...
}
}
Should be:
if let data = response.data {
do {
let json = try JSON(data: data)
...
} catch {
...
}
}
Use if let, guard let to unwrap, avoid using force unwrap.

Related

How Do I Send a Dictionary to a Client using Vapor Websockets

I've continued this question here, because the focus has changed, but is related.
Vapor is communicating between a server and and iOS client. Just setup code, for learning purposes.
Now, I want to send a dictionary of values via the established connection using JSON. I'm getting caught up in the unexplained logic of demo code, but here's where I am, in my vapor routes:
app.webSocket("respond") { req, ws in
ws.onText { ws, text in
print(text)
let msgDict = "{'name' = 'bobby'}"
let encoder = JSONEncoder()
let data = try encoder.encode(msgDict)
ws.send(data)
}
}
This won't compile: Invalid conversion from throwing function of type '(WebSocket, String) throws -> ()' to non-throwing function type '(WebSocket, String) -> ()'
while I generally understand this error, and dependent on how I play with this it varies.
What generally I'm looking for is a simple pattern to take internal dictionary values, encode them into JSON, and send them. What am I not doing, or not doing right?
I see two problems with that code.
The first one, explained by the compiling error, it's actually telling that it will not handle any errors thrown. When you do encoder.encode(msgDict), this code can throw an Error, and you're not handling this possible error anywhere. If you handle that possible error, code you wrote in that closure will have the expected type. You have a few options to do so:
Wrap the code around a do-catch block
do {
let data = try encoder.encode(msgDict)
ws.send(data)
} catch let error {
// Handle error
}
Use try?, means you do not handle the error but get nil as a result, if an error occurred.
if let data = try? encoder.encode(msgDict) {
ws.send(data)
}
Use try!, means you force-unwrap the result (not advisable, but you can try, if you're 100% sure)
let data = try! encoder.encode(msgDict)
ws.send(data)
The second problem is how you're writing that response - "{'name' = 'bobby'}". This is an invalid JSON, you should use double quotes instead:
let msgDict = "{\"name\" = \"bobby\"}"
You can also use Dictionary, as long as the content is of type Encodable:
let msgDict = ["name": "bobby"]
You can also use JSONEncoder to encode any instances of classes that conform to Encodable.
So the whole thing, I'd write like this:
app.webSocket("respond") { req, ws in
ws.onText { ws, text in
print(text)
let msgDict = ["name": "bobby"]
let encoder = JSONEncoder()
do {
let data = try encoder.encode(msgDict)
ws.send(data)
}
catch let error {
print("An error occurred: \(error)")
}
}
}
The error tells you that a do - catch block is missing
do {
let encoder = JSONEncoder()
let data = try encoder.encode(msgDict)
ws.send(data)
} catch { print(error) }
However your approach to create JSON cannot work because msgDict is neither valid JSON nor a Swift dictionary.
To encode the dictionary with JSONEncoder it must be a Swift dictionary
let msgDict = ["name":"bobby"]
But JSONEncoder is overkill. A simpler way is to create the JSON dictionary literally
app.webSocket("respond") { req, ws in
ws.onText { ws, text in
print(text)
let msgDict = #"{"name":"bobby"}"#
ws.send(Data(msgDict.utf8))
}
}

The Data Couldn't Be Read Because It Isn't in The Correct Format?

I'm pretty sure my model is correct based on my data, I cannot figure out why I am getting the format error?
JSON:
{
"1596193200":{
"clientref":1,
"type":"breakfast"
},
"1596200400":{
"clientref":0,
"type":"lunch"
},
"1596218400":{
"clientref":2,
"type":"dinner"
}
}
model:
struct Call: Decodable {
let clientref: Int?
let type: String?
}
edit updated question with the code for decoding the json data from the URL:
class CallService {
static let shared = CallService()
let CALLS_URL = "url.com/Calls.json"
func fetchCalls(completion: #escaping ([Call]) -> ()) {
guard let url = URL(string: CALLS_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error.localizedDescription)
return
}
guard let data = data else {return}
do {
let call = try JSONDecoder().decode([Call].self, from: data)
completion(call)
} catch let error {
print("Failed to create JSON with error: ", error.localizedDescription)
}
}.resume()
}
}
I strongly suggest to learn how to debug: it includes where to look, what info to get, where to get them, etc, and at the end, fix it.
That's a good thing that you print the error, most beginner don't.
print("Failed to create JSON with error: ", error.localizedDescription)
=>
print("Failed to create JSON with error: ", error)
You'll get a better idea.
Second, if it failed, print the data stringified. You're supposed to have JSON, that's right. But how often do I see question about that issue, when it fact, the answer wasn't JSON at all (the API never stated it will return JSON), the author were facing an error (custom 404, etc.) and did get a XML/HTML message error etc.
So, when the parsing fails, I suggest to do:
print("Failed with data: \(String(data: data, encoding: .utf8))")
Check that the output is a valid JSON (plenty of online validators or apps that do that).
Now:
I'm pretty sure my model is correct based on my data,
Well, yes and no.
Little tip with Codable when debuting (and not using nested stuff): Do the reverse.
Make your struct Codable if it's not the case yet (I used Playgrounds)
struct Call: Codable {
let clientref: Int?
let type: String?
}
do {
let calls: [Call] = [Call(clientref: 1, type: "breakfast"),
Call(clientref: 0, type: "lunch"),
Call(clientref: 2, type: "dinner")]
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
let jsonData = try encoder.encode(calls)
let jsonStringified = String(data: jsonData, encoding: .utf8)
if let string = jsonStringified {
print(string)
}
} catch {
print("Got error: \(error)")
}
Output:
[
{
"clientref" : 1,
"type" : "breakfast"
},
{
"clientref" : 0,
"type" : "lunch"
},
{
"clientref" : 2,
"type" : "dinner"
}
]
It doesn't look like. I could only used an array to put various calls inside a single variable, and that's what you meant for decoding, because you wrote [Call].self, so you were expecting an array of Call. We are missing the "1596218400" parts. Wait, could it be a dictionary at top level? Yes. You can see the {} and the fact it uses "keys", not listing one after the others...
Wait, but now that we printed the full error, does it make more sense now?
typeMismatch(Swift.Array<Any>,
Swift.DecodingError.Context(codingPath: [],
debugDescription: "Expected to decode Array<Any> but found a dictionary instead.",
underlyingError: nil))
Fix:
let dictionary = try JSONDecoder().decode([String: Call].self, from: data)
completion(dictionary.values) //since I guess you only want the Call objects, not the keys with the numbers.
From the code you provided it looks like you are trying to decode an Array<Call>, but in the JSON the data is formatted as a Dictionary<String: Call>.
You should try:
let call = try JsonDecoder().decode(Dictionary<String: Call>.self, from: data)

How to keep certain changed properties unchanged in Realm migration - Swift

When you have a Realm model that looks something like:
class Thing: Object, Decodable {
#objc dynamic var id: String = ""
#objc dynamic var propertyOne: String = ""
#objc dynamic var propertyTwo: String? = nil
override static func primaryKey() -> String? {
return "id"
}
}
All of the data is from a JSON file, using a JSON Serializer:
func serialize(input sourceName: String) {
let path = Bundle.main.path(forResource: sourceName, ofType: nil)
let url = URL(fileURLWithPath: path!)
let jsonDecoder = JSONDecoder()
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
guard json is [AnyObject] else {
assert(false, "failed to parse")
return
}
do {
let things = try jsonDecoder.decode([Thing].self, from: data)
let realm = try! Realm()
for thing in things {
try! realm.write {
realm.add(thing)
// realm.add(thing, update: .modified) wipes out all changes
}
}
} catch let error {
print("failed to convert data: \(error)")
}
} catch let error {
print(error)
}
}
propertyOne's info is gotten from the JSON. propertyTwo's value is meant to be inputted from within the app. Once I serialize the JSON again, all changes to propertyTwo are wiped out. How do I make a migration without restoring it to the original JSON file? I want to keep some properties unchanged during a migration/new serialization.
In my AppDelegate, this is my code in didFinishLaunchingWithOptions:
let serializer = JSONSerializer()
serializer.serialize(input: "Nafliah.json")
This works if I have realm.add(thing, update: .modified) in my serializer. But this wipes out all changes made within the app. If I change it to this:
if save.bool(forKey: "serilized") == false {
let serializer = JSONSerializer()
serializer.serialize(input: "Nafliah.json")
save.set(true, forKey: "serilized")
}
And make the serializer part realm.add(thing), it works and does not wipe out changed data every time the app is opened.
The issue is, once I add a new object to the JSON file, it obviously does not get added to the realm. I must serialize the JSON again. This, however, deletes all changes done in the 2 properties that are only meant to be changed by the user in the app.
I figured it out. You have to do a partial serialization.
Changed this:
realm.add(thing, update: .modified)
To:
realm.create(Thing.self, value: [
"id": thing.id,
"propertyOne": thing.propertyOne
// Leave out propertyTwo to leave it unchanged
], update: .modified)
Please check below link for Migrating Realm for existing properties
https://docs.realm.io/sync/v/3.x/using-synced-realms/migrating-your-data

Swift ObjectMapper: How to parse JSON with backslash

I have tried pretty much all the potential solutions on stackoverflow and so far no luck,
This is my json response:
[
"{\"id\":5,\"request_id\":\"rqst5c17fc752d44f1.15452158\",\"business_name\":\"611 Solutions\",\"business_email\":\"611thesolutions#gmail.com\",\"title\":\"123ABC - TESTING\",\"details\":\"Package is fragile, please haul with care\",\"load_description\":\"Royal Timber\",\"amount_offered\":\"2500\",\"pickup_address\":\"123 Colliumeal Dr, Fort Wayne, Indiana\",\"dropoff_address\":\"647 Airportway, Chicago, Illinois\",\"timestamp\":\"2018-12-17 19:43:49\"}"
]
Notice there are backslashes within the key and values of the json and my parsing is failing, this is how I am parse the json:
Alamofire.request(JOB_REQUEST_BASE_URL, method: .post, parameters: parameter, encoding: URLEncoding(), headers: nil).responseArray { (response: DataResponse<[JobResponseDataObject]>) in
log.debug("Fetching Job Requests...")
switch response.result {
case .success(let responseArray) :
log.debug(response.debugDescription)
log.debug("Sucessfully fetch job requests")
log.debug("Job request counts: \(responseArray.count)")
completionHandler(JobRequest.fetchJobRequest.Response(jobResponses: responseArray), nil)
case .failure(let error) :
log.debug("Fetching error: JobRequest")
log.debug(error.localizedDescription)
completionHandler(nil, .FailedToFetchEmptyJobRequests)
}
}
I have also tried fetching the pure string using .responseString and doing let json = response.result.value?.replacingOccurrences(of: "\\", with: "") and mapping it like so let jobs = Mapper<JobResponseDataObject>().map(JSONString: json!) so far no luck too. Please help
Thanks
You can try
if let str = responseArray.first as? String , let data = str.data(using:.utf8) {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode(Root.self,from:data)
}
catch {
print(error)
}
}
struct Root: Codable {
let id: Int
let requestId, businessName, businessEmail, title: String
let details, loadDescription, amountOffered, pickupAddress: String
let dropoffAddress, timestamp: String
}
You don't need to remove backslashes - it's just serilized one more time, that means it needs to deserialize back.
Look at: Why json response includes backward slashes in web api response
Just make a Data object from the string item:
let data = stringItem.data(using: .utf8)
then decode normally using JSONDecoder.

Parsing json response with nested " in swift

I wanted to know the best way to parse json response of below type in Swift 4. Response is double encoded -
\"[{\\"value\\":\\"International University \\\\"MITSO\\\\"\\",\\"id\\":\\"a1v24000000uOrPAAU\\",\\"addlFields\\":[\\"Mi?narodny Universitet \\\\"MITSO\\\\"\\"]}]\"
Here is the data in NSData format -
(String) $R0 = "data: Optional(146 bytes) as NSData: <225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>"
As you see value of the key "value" has a inner double quotes(").
JSONSerialization consider this as invalid Json.
Any help will be greatly appreciated.
The content of your data as String is as follows:
"[{\"value\":\"International University \\\"MITSO\\\"\",\"id\":\"a1v24000000uOrPAAU\",\"addlFields\":[\"Mi?narodny Universitet \\\"MITSO\\\"\"]}]"
Seeing the actual content without extra double-quotes and backslashes needed to show String as String-literal, it looks like some valid JSON is embedded in a String.
This may happen when the server side code double-encodes the data. You should better tell your server side engineer to fix the issue, but if it is difficult or would take long time, you can double-decode it.
Testing code:
import Foundation
let dataStr = "<225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>".dropFirst().dropLast().replacingOccurrences(of: " ", with: "")
let byteArr = stride(from: 0, to: dataStr.count, by: 2).map{(index: Int)->UInt8 in
let start = dataStr.index(dataStr.startIndex, offsetBy: index)
let end = dataStr.index(start, offsetBy: 2)
return UInt8(dataStr[start..<end], radix: 16)!
}
let responseData = Data(bytes: byteArr)
print(responseData as NSData)
Check here, whether the print statement output is exactly the same as your sample response. (If you want to test the following code with your actual data than sample response, use just let responseData = result as! Data instead of above lines.)
So, you just need to use JSONSerialization twice:
block: do {
let firstDecoded = try JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as! String
let firstDecodedData = firstDecoded.data(using: .utf8)!
let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData)
//Code below is an example of using decoded result.
guard let resultArray = secondDecoded as? [[String: Any]] else {
print("result is not an Array of Dictionary")
break block
}
print(resultArray)
if
let addlFields = resultArray[0]["addlFields"] as? [String],
let firstAddl = addlFields.first
{
print(firstAddl)
}
} catch {
print(error)
}
Outputs: (Omitting some output for print(responseData as NSData).)
[["id": a1v24000000uOrPAAU, "value": International University "MITSO", "addlFields": <__NSSingleObjectArrayI 0x100e40c80>(
Mi?narodny Universitet "MITSO"
)
]]
Mi?narodny Universitet "MITSO"
(You may find some parts like <__NSSingleObjectArrayI 0x100e40c80> are strange, but it's just a problem of generating default description and you can access the elements as an Array.)
Anyway, please try and see what you can get with my code above.
#OOPer thank you for the solution. Appreciate you giving your time.
Solution worked as expected. Pasting code here which may help others.
Here is how I am doing -
func getData(text:String, callback:#escaping (_ result: Array<somedata>?,_ error:Error?) -> Void) {
let params = ["search":text]
getDataSomeAPI(url: "http:\\xyz.com\fdf", params: params) { (result, error) in
if error == nil {
do {
//Response is double encoded
if let firstDecoded = try JSONSerialization.jsonObject(with: result as! Data, options: .allowFragments) as? String
{
let firstDecodedData = firstDecoded.data(using: .utf8)!
if let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData) as? NSArray {
var array = [somedata]()
for obj in secondDecoded {
Mapper<somedata>().map(JSONObject: obj).then { mappedObj in
array.append(mappedObj)
}
}
callback(array,nil)
}
}
}
catch {
//Handle unexpected data format
let error = NSError(domain: "",
code: 0,
userInfo: nil)
let sErr = Error(err: error)
callback(nil, sErr)
}
} else {
callback(nil, error)
}
}
}