I am using CocoaAsyncSocket to retrieve a message from a servers API that's using JSON. I am able to get the data and convert it to a printable string, what I am unable to do is retrieve a value (transactionId) from my attempts to parse the JSON string I already have, using SwiftyJSON. I know there are other posts that are similar to this one but none have solved my problem.
In ViewController:
func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
guard let msg = String(data: data, encoding: .utf8) else { return }
var response = " "
if msg.contains("Reset") {
transactionID = ParseJSON().parse(message: msg)
response = String(format: "{\"Response\":{\"transactionId\":\"%#%\",\"content\":{\"Reset\":{}}}}", transactionID)
socket.write((response.data(using: .utf8))!, withTimeout: -1, tag: 0)
}
socket?.readData(withTimeout: -1, tag: 0)
}
ParseJSON class:
func parse (message: String) -> String {
var parsedMessage = " "
let json = JSON(parseJSON: message)
let transactionId = json["Request"]["transactionId"].stringValue
parsedMessage = transactionId
print(parsedMessage)
return parsedMessage
}
The result that is displayed is an empty transactionId value. Nothing prints or anything.
If you spot any errors in my code or have a better approach then please let me know!
Edit:
Here is the string I am attempting to parse:
{"Request": {"content": {"Reset": {}}, "transactionId": "f7c4d630-552b-46d9-a37d-44450537b48d"}}
Here is my output:
{\"Response\":{\"transactionId\":\"\",\"content\":{\"Reset\":{}}}}
The problem is not the code. Consider:
func parse(message: String) -> String {
let json = JSON(parseJSON: message)
return json["Request"]["transactionId"].stringValue
}
let input = """
{"Request": {"content": {"Reset": {}}, "transactionId": "f7c4d630-552b-46d9-a37d-44450537b48d"}}
"""
let transactionID = parse(message: input)
print("transactionId:", transactionID)
let response = String(format: "{\"Response\":{\"transactionId\":\"%#\",\"content\":{\"Reset\":{}}}}", transactionID)
print("response:", response)
The result of the above, as you’d expect, is:
transactionId: f7c4d630-552b-46d9-a37d-44450537b48d
response: {"Response":{"transactionId":"f7c4d630-552b-46d9-a37d-44450537b48d","content":{"Reset":{}}}}
So I suspect that the input message is not quite what you expected. So I might suggest adding some error handling so you can diagnose precisely where it is going wrong:
func parse(message: String) -> String {
let json = JSON(parseJSON: message)
if let error = json.error {
fatalError("JSON parsing error: \(error)")
}
let request = json["Request"]
if let error = request.error {
fatalError("request error: \(error)")
}
let transactionId = request["transactionId"]
if let error = transactionId.error {
fatalError("transactionId error: \(error)")
}
return transactionId.stringValue
}
Now, in practice, you probably wouldn’t use fatalError, but rather would do some graceful error handling (e.g. change parse such that it throws, then throw the errors encountered, if any, and then catch the error where you call parse and handle any runtime issues gracefully). But during this diagnostic process, fatalError is useful because it will stop your debugger at the offending line, simplifying your diagnostic process.
Bottom line, the request must not be quite in the form you expect. Note, it’s going to be very sensitive to capitalization, malformed JSON, etc. So, by looking at the errors provided by SwiftyJSON, you should be able to narrow down the issue quite quickly.
Below, you tell us that the Data is:
<7b225265 71756573 74223a20 7b22636f 6e74656e 74223a20 7b225265 73657422
3a207b7d 7d2c2022 7472616e 73616374 696f6e49 64223a20 22643937 36303036
622d3130 38302d34 3864652d 39323232 2d623139 63363663 35303164 31227d7d
00>
The problem is that last byte, 0x00. If you remove that, it works.
FWIW, when I convert that hex string back to a Data and run it through JSONSerialization, it confirms the diagnosis:
Error Domain=NSCocoaErrorDomain
Code=3840 "Garbage at end."
UserInfo={NSDebugDescription=Garbage at end.}
You need to figure out why that 0x00 was included in the end of your payload and remove it.
Related
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))
}
}
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)
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.
[
{
"ID": 0,
"Name": "PHI"
},
{
"ID": 0,
"Name": "ATL"
}
]
I'm using SwiftyJSON and Alamofire. This is what is being returned. I want to loop through each of these objects now in my code. I'm having trouble getting this information though.
json[0]["Name"].string seems to return nil, and I'm not sure why. The JSON object is definitely getting the JSON, when I print it to the console it looks exactly like above.
I also tried:
var name = json[0].dictionary?["Name"]
Still nil though.
Any ideas?
EDIT
Here's the code I'm using to retrieve the JSON.
Alamofire.request(.GET, "url", parameters: nil, encoding: .JSON, headers: nil).responseJSON
{
(request, response, data, error) in
var json = JSON(data!)
//var name = json[0].dictionary?["Name"]
}
Your JSON is valid (at least this snippet is), and your first method to retrieve the data from SwiftyJSON is correct.
On the other hand, the Alamofire snippet you showed didn't compile for me, I had to change the signature.
Given that in the comments you say that not only json[0]["Name"] is nil but json[0] is also nil, I think you have a problem with how your data is fetched.
I tested this version of the Alamofire method in a sample app and it worked well:
Alamofire.request(.GET, yourURL).responseJSON(options: nil) { (request, response, data, error) in
let json = JSON(data!)
let name = json[0]["Name"].string
println(name)
}
In the screenshot, the URL is my local server, with your JSON copy pasted in "test.json".
This "answer" is an extension of my comment, I needed room to show more info...
I think you might try this.
first convert JSON(data) using swifyJSON
let json = JSON(data!)
then count the array element in data.
let count: Int? = json.array?.count
after that use for loop to reach array element one.
for index in 0...count-1 {
self.idArray.append(json[index]["ID"].stringValue)
self.nameArray.append(json[index]["Name"].stringValue)
}
you will get sorted data in "idArray" and "nameArray". try to print both the array.
You do not have to use index to parse array. You can get single json object from array and use it to access the data:
for (index: String, subJson: JSON) in json {
println("Current Index = \(index)")
if let _id = subJson["ID"].int {
println("ID = \(_id)")
} else {
println("Error ID")
}
if let _name = subJson["Name"].string {
println("Name = \(_name)")
} else {
println("Error Name")
}
}
I am trying to make a POST request in Alamofire to return a JSON object. This code worked in Swift 1, but in swift 2 I get this invalid parameter issue:
Tuple types '(NSURLRequest?, NSHTTPURLResponse?, Result<AnyObject>)' (aka '(Optional<NSURLRequest>, Optional<NSHTTPURLResponse>, Result<AnyObject>)') and '(_, _, _, _)' have a different number of elements (3 vs. 4)
It seems like the error parameter was removed, but I am using the error parameter inside the function to check for errors, so how would I do that without an error param?
Here's my code for the POST request:
let response = Alamofire.request(.POST, urlPath, parameters: parameters, encoding: .URL)
.responseJSON { (request, response, data, error) in
if let anError = error
{
// got an error in getting the data, need to handle it
print("error calling POST on /posts")
print(error)
}
else if let data: AnyObject = data
{
// handle the results as JSON, without a bunch of nested if loops
let post = JSON(data)
// to make sure it posted, print the results
print("The post is: " + post.description)
}
}
If you see the documentation in the branch Swift2.0 you can see that the responseJSON function has changed as the error says, it have now three parameters but you can catch the error too, lets take a look:
public func responseJSON(
options options: NSJSONReadingOptions = .AllowFragments,
completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<AnyObject>) -> Void)
-> Self
{
return response(
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: completionHandler
)
}
Now it returns an enum Result<AnyObject> and according to the doc :
Used to represent whether a request was successful or encountered an error.
- Success: The request and all post processing operations were successful resulting in the serialization of the
provided associated value.
- Failure: The request encountered an error resulting in a failure. The associated values are the original data
provided by the server as well as the error that caused the failure.
And it have inside an property entitled error, with the following doc:
/// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: ErrorType? {
switch self {
case .Success:
return nil
case .Failure(_, let error):
return error
}
}
Then if you follow this test case inside Alamofire you can see how to get the error properly:
func testThatResponseJSONReturnsSuccessResultWithValidJSON() {
// Given
let URLString = "https://httpbin.org/get"
let expectation = expectationWithDescription("request should succeed")
var request: NSURLRequest?
var response: NSHTTPURLResponse?
var result: Result<AnyObject>!
// When
Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
.responseJSON { responseRequest, responseResponse, responseResult in
request = responseRequest
response = responseResponse
result = responseResult
expectation.fulfill()
}
waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(request, "request should not be nil")
XCTAssertNotNil(response, "response should not be nil")
XCTAssertTrue(result.isSuccess, "result should be success")
XCTAssertNotNil(result.value, "result value should not be nil")
XCTAssertNil(result.data, "result data should be nil")
XCTAssertTrue(result.error == nil, "result error should be nil")
}
UPDATE:
Alamofire 3.0.0 introduces a Response struct. All response serializers (with the exception of response) return a generic Response struct.
public struct Response<Value, Error: ErrorType> {
/// The URL request sent to the server.
public let request: NSURLRequest?
/// The server's response to the URL request.
public let response: NSHTTPURLResponse?
/// The data returned by the server.
public let data: NSData?
/// The result of response serialization.
public let result: Result<Value, Error>
}
So you can call it like the following way:
Alamofire.request(.GET, "http://httpbin.org/get")
.responseJSON { response in
debugPrint(response)
}
You can read more about the migration process in the Alamofire 3.0 Migration Guide.
I hope this help you.
Have you tried using three parameters in the .responseJSON and inserting a try catch block around the areas you want error logging, check out this link if you need help with swift 2 try catchs