I'm using SwifyJSON to parse some JSON sent to my iOS app through socket.io and the .dictionaryValue of the response is nil. Here's how the data is sent from the server:
socket.emit('hasBeenMatched', {user: JSON.stringify(currentUser)});
Here's what I've got in my iOS app:
socket.on("hasBeenMatched", callback: {data, ack in
println("got response after requesting match");
let user = JSON(data!)
println(user)
println(user[0])
println(user[0]["user"])
println(user[0]["user"].dictionaryValue)
})
And here's the output of that code:
got response after requesting match
[
{
"user" : "{\"_id\":\"5511c3d8abcdc2fcf7b8fe4b\",\"email\":\"j\",\"password\":null,\"firstname\":\"j\",\"lastname\":\"j\",\"age\":9,\"radius\":\"9\",\"__v\":0,\"wantsToBeMatched\":true,\"matchedWith\":\"k k\"}"
}
]
{
"user" : "{\"_id\":\"5511c3d8abcdc2fcf7b8fe4b\",\"email\":\"j\",\"password\":null,\"firstname\":\"j\",\"lastname\":\"j\",\"age\":9,\"radius\":\"9\",\"__v\":0,\"wantsToBeMatched\":true,\"matchedWith\":\"k k\"}"
}
{"_id":"5511c3d8abcdc2fcf7b8fe4b","email":"j","password":null,"firstname":"j","lastname":"j","age":9,"radius":"9","__v":0,"wantsToBeMatched":true,"matchedWith":"k k"}
[:]
In an alternate part of my code I have the following code:
let request = Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
request.validate()
request.response { [weak self] request, response, data, error in
if let strongSelf = self {
// Handle various error cases here....
var serializationError: NSError?
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments, error: &serializationError) {
println(JSON(json).dictionaryValue)
// Story board navigation
} else {
//Handle error case
}
}
}
EDIT: the output of the println in the Alamofire response handling looks like:
[_id: 5511c3d8abcdc2fcf7b8fe4b, password: null, __v: 0, lastname: j, age: 9, wantsToBeMatched: true, firstname: j, radius: 9, email: j, matchedWith: k k]
What I'm wondering is: why does println(user[0]["user"].dictionaryValue) result in [:]?
Figured it out, but my solution isn't related to SwiftyJSON (which I'm still curious about). I changed how my server sends data over socket. I changed socket.emit('hasBeenMatched', {user: JSON.stringify(currentUser)}); to socket.emit('hasBeenMatched', {user: currentUser});. Essentially, I removed the manual JSON-ification.
Related
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)
I'm trying to query the NASA image API (latest docs here) using Swift 4. I set up and tested my request with JSONPlaceholder to make sure my network request and decoding was setup correctly. Everything was working fine, but when I switched the URL and corresponding JSON data structure, I get an error saying 'the data couldn't be read because it is missing.'
I've used Postman to verify that JSON is being returned, and to build the JSON data structure.
Is this a common error from decoding JSON or is it something with the network request? Or am I missing something with using the NASA API?
let NASAURL = URL(string: "https://images-api.nasa.gov/search?q=moon")
let session = URLSession(configuration: .default)
let task = session.dataTask(with: NASAURL!) { (rdata, response, error) in
NSLog("Data Description: " + (rdata!.debugDescription) + "\nResponse: " + response.debugDescription + "\nError Description: " + error.debugDescription)
guard rdata != nil else{
NSLog("No data")
return
}
guard error == nil else{
NSLog(response.debugDescription + "\n")
NSLog(error.debugDescription)
NSLog(error.debugDescription)
return
}
let decoder = JSONDecoder()
do{
NSLog(rdata.debugDescription)
let usr = try decoder.decode(Collect.self, from: rdata!) // Throws
NSLog(usr.href)
} catch {
NSLog("Error: " + error.localizedDescription)
}
}
task.resume()
// Collect is in its own class/file
struct Collect: Codable {
var href: String
//var items: [Items]
}
Below is the printout from the above log statements...
2017-09-29 19:50:24.135288-0500 OpenNASA[16993:10774203] Data Description: 67669 bytes
Response: Optional(<NSHTTPURLResponse: 0x60000003db00> { URL: https://images-api.nasa.gov/search?q=moon } { status code: 200, headers {
"Access-Control-Allow-Origin" = "*";
"Cache-Control" = "public, max-age=300, s-maxage=600";
"Content-Encoding" = gzip;
"Content-Length" = 9334;
"Content-Type" = "application/json; charset=UTF-8";
Date = "Sat, 30 Sep 2017 00:48:11 GMT";
Server = "nginx/1.4.6 (Ubuntu)";
"Strict-Transport-Security" = "max-age=31536000";
Vary = "Accept-Encoding";
"access-control-allow-headers" = "Origin,Content-Type,Accept,Authorization,X-Requested-With";
"access-control-allow-methods" = GET;
} })
Error Description: nil
2017-09-29 19:50:24.137324-0500 OpenNASA[16993:10774203] Optional(67669 bytes)
2017-09-29 19:56:01.843750-0500 OpenNASA[16993:10774203] Error: The data couldn’t be read because it is missing.
You Codable should be like below:
struct Collect: Codable {
var collection: Links
}
struct Links: Codable {
var links: [Href]
}
struct Href: Codable {
var href: String
}
You have to call like below:
let usr = try decoder.decode(Collect.self, from: rdata!) // Throws
let links = usr.collection.links
for link in links {
print(link.href)
}
I have tried a lot of things now and are still getting:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (_SwiftValue)'
UPDATE:
I scan a barcode and save the info:
let calendar = Calendar.current
let expiryDate = (calendar as NSCalendar).date(byAdding: .month, value: 3, to: Date(), options: [])?.description
let barcode = BarcodeData(barcodeValue: value,
datetime: dateTime,
expiryDate: expiryDate!,
latitude: latitude.description,
longitude: longitude.description,
status: txtStatus.text!,
type: txtType.text!,
extraText: "")
Object are then mapped to a JSON string, it seems that the slashes (/) are added by this function:
let jsonBarcode = Mapper<BarcodeData>().toJSONString(barcode)
The barcode are then added to a list of String:
barcodeDataList.append(jsonBarcode)
When I click a button I invoke the web service, that anticipate parameters in the form of:
let testParams : Parameters =
[ "udid": "my_udid",
"data": jsonArray
]
jsonArray consist of an array of the BarcodeData-object(s) as seen above.
Communication with the web service looks like:
Alamofire.request(url, method: .post, parameters: testParams, encoding: JSONEncoding.default).validate().responseJSON { response in
switch response.result {
case .success:
print("Validation successful")
if let json = response.result.value {
print("JSON: \(json)")
}
case .failure(let error):
print("Error: \(error)")
}
}
The following is passed to the ws:
["udid": "\"001-my_udid\"", "data": [
"{\"latitude\":\"0.0\",\"status\":\"Empty\",\"datetime\":\"2016-09-20 05:10\",\"longitude\":\"0.0\",\"type\":\"ABC123\",\"barcodevalue\":\"123456\"}"
]]
The json array for "data" validates at jsonlint.com and the response from the server is in the form of a json object like:
{result: "Data successfully received"}
Change your encoding in request from encoding: JSONEncoding.default to encoding: URLEncoding.default
I think you need to encode your array in json object try something like this
do{
let data = try NSJSONSerialization.dataWithJSONObject(mappingArray, options: NSJSONWritingOptions(rawValue: 0))
let mappedString : String = (String(data: data , encoding: NSUTF8StringEncoding)!)
return mappedString
}
catch{
print(error)
return error as! String
}
Append this string with your url
I am new to swift and I am trying to parse some simple JSON data I am retrieving from a private API that I have. I am using the SwiftJSON library.
No matter what I do I cannot assign the variable "videoUploadId" with the value of "video_upload_id" coming from the JSON response. I hope i provided enough info to get some help. Thanks
Here is a segment of the code
let task = session.dataTaskWithRequest(request, completionHandler: { (data : NSData!, response, error : NSError!) -> Void in
if (error == nil) {
// Success
let statusCode = (response as NSHTTPURLResponse).statusCode
println("Status Code: \(statusCode)\r\n")
println("Response: \(response)\r\n")
println("Data: \(data)\r\n")
let dataContent = NSString(data: data!, encoding: NSUTF8StringEncoding)!
println("UTF8 Data: \(dataContent)\r\n")
let json = JSON(dataContent)
if let videoUploadId = json["video_upload_id"].int {
println("Video Upload ID (Dict: int): \(videoUploadId)")
}
else if let videoUploadId = json["video_upload_id"].string {
println("Video Upload ID (Dict: string): \(videoUploadId)")
}
else if let videoUploadId = json[0].int {
println("Video Upload ID (Array: int): \(videoUploadId)")
}
else if let videoUploadId = json[0].string {
println("Video Upload ID (Array: string): \(videoUploadId)")
}
else {
println(json["video_upload_id"].error)
}
}
else {
// Failure
println("URL Session Task Failed: %#", error.localizedDescription);
}
})
task.resume()
This is what I am receiving from my console:
Login Response: HTTP 200
Status Code: 201
Response: { URL: https:////videos/uploads/ } { status code: 201, headers {
Connection = "Keep-alive";
"Content-Length" = 24;
"Content-Type" = "application/json";
Date = "Sun, 25 Jan 2015 01:02:42 GMT";
Location = "https:////videos/uploads/";
Server = "Apache/2.2.15 (CentOS)";
"Set-Cookie" = "session=eyJzZXNzaW9uX3Rva2VuIjp7IiBiIjoiUzAxWGFYRnlVVGM1YjBsa1kxWkJiV2xrYVZwcFZXdDFiR0ZLYW5GQ1VqRjFjbk5GIn19.B6XSMg.HXatQ76ZFaoZEQsnNu1BgsVECKA; HttpOnly; Path=/";
} }
Data: <7b227669 64656f5f 75706c6f 61645f69 64223a20 3736307d>
UTF8 Data: {"video_upload_id": 760}
Optional(Error Domain=SwiftyJSONErrorDomain Code=901 "Dictionary["video_upload_id"] failure, It is not an dictionary" UserInfo=0x170238620 {NSLocalizedDescription=Dictionary["video_upload_id"] failure, It is not an dictionary})
As you can see from the code and console output, I am attempting to set the variable in several different ways all of which seem to fail. I am receiving the error "Dictionary["video_upload_id"] failure, It is not an dictionary" I even tried prepending "[" and appending "]" to try to see if its a formatting issue.
Any clues?
You are doing the initialization wrong. You should use:
let json = JSON(data:data) // data is NSData!
Converting NSData to NSString is not necessary, and somehow wrong for this. SwiftyJSON can only be initialized with NSData or Swift object.
I want to make a GET request in swift to get some Json data.
I tried to use AFNetworking and it works, but I don't know how to return the Json I get.
I tried with a return but it's made before the GET so I get nothing...
func makeGet(place:String) -> String
{
var str:String = ""
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer.setValue("608c6c08443c6d933576b90966b727358d0066b4", forHTTPHeaderField: "X-Auth-Token")
manager.GET("http://something.com/api/\(place)",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
str = "JSON: \(responseObject.description)"
println(str) //print the good thing
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
str = "Error: \(error.localizedDescription)"
})
return str //return ""
}
Can you help me ?
Since you want to return the value after the webservice request is completed you have to pass the data via a delegate or a block(In swift it is called closures)
I see blocks useful here
//Above your class file create a handler alias
typealias SomeHandler = (String! , Bool!) -> Void
func makeGet(place:String , completionHandler: SomeHandler!)
{
var str:String = ""
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer.setValue("608c6c08443c6d933576b90966b727358d0066b4", forHTTPHeaderField: "X-Auth-Token")
manager.GET("http://something.com/api/\(place)",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
str = "JSON: \(responseObject.description)"
println(str) //print the good thing
completionHandler(str,false) //str as response json, false as error value
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
str = "Error: \(error.localizedDescription)"
completionHandler("Error",true)
})
//return str //return "" You don't want to return anything here
}
When you want to call the method get the values like this
makeGet(){
yourJSONString , errorValue in //Here the value will be passed after you get the response
if !errorValue {
println("The End."
}
}
More about swift closures
FYI: AFNetworking owner has created a new Networking layer for swift and it is called Alamofire (AF from AFNetworking is Alamofire :])
You're not getting a response from that function, because the GET operation happens asynchronously. That is, the order of execution looks like this:
You call makeGet
makeGet creates manager, which fires off a GET request
makeGet finishes executing and returns an empty string
(some time later) manager receives a value back from the server and executes either the success or failure block.
So the only time you have access to the JSON that comes back from the server is in step 4, and you need to find a way of storing that value so you can parse it or use it or whatever. There are a variety of options here -- one is to define closures that call event handlers on your class instance, like this:
class MyClass {
func jsonLoaded(json: String) {
println("JSON: \(json)")
}
func jsonFailed(error: NSError) {
println("Error: \(error.localizedDescription)")
}
func makeGet(place:String) {
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer.setValue("608c6c08443c6d933576b90966b727358d0066b4", forHTTPHeaderField: "X-Auth-Token")
manager.GET("http://something.com/api/\(place)",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
self.jsonLoaded(responseObject.description)
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
self.jsonFailed(error)
}
)
}
}
let manager = AFHTTPSessionManager()
manager.GET("http://api.androidhive.info/json/movies.json", parameters: nil, success: { (operation, responseObject) -> Void in
print(responseObject)
}, failure: nil)
AFHTTPRequestOperationManager is not available in latest AFnetworking library, it has replaced with AFHTTPSessionManager.
This is a simple example of getting response object.