I trying to decode an json object from a webrequest with Swift 3, Gloss 1.1 and Alamofire 4.0:
The json response looks like this:
{
"code": "0",
"message": "OK.",
"data": [
{
"timestamp": 1480885860,
"open": 10.99
},
{
"timestamp": 1480886040,
"open": 11
}
]
}
My json Decodables are the following two:
struct ResponseJsonModel : Decodable {
let code : Int
let message : String
let data : [MarketPriceJsonModel]?
// <~~
init?(json: JSON) {
guard let codeInt : Int = "code" <~~ json else {
print("code unwrapping failed in guard")
return nil
}
guard let messageStr : String = "message" <~~ json else {
print("message unwrapping failed in guard")
return nil
}
self.code = codeInt
self.message = messageStr
self.data = "data" <~~ json
}
}
struct MarketPriceJsonModel : Decodable {
let timestamp : NSDate
let open : Double
init?(json: JSON) {
guard let timestampInt : Int = "timestamp" <~~ json else {
print("timestamp unwrapping failed in guard")
return nil
}
guard let open : Double = "open" <~~ json else {
print("open price unwrapping failed in guard")
return nil
}
self.timestamp = NSDate(timeIntervalSince1970: Double(timestampInt))
self.open = open
}
}
I'm new to Gloss and don't understand why the initalize of my decodable-Object fails.
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success:
guard let value = response.result.value as? JSON,
let responseModel = ResponseJsonModel(json: value) else {
print("responseModel failed")
return
}
print(responseModel.message)
case .failure(let error):
print(error)
}
}
The output of the code is
code unwrapping failed in guard
responseModel failed
but why?
When I add a breakpoint in init?() and look at the json-variable in the debug area, so the request looks ok but the parsing fails.
Is there a way to get better exception messages in case something fails?
Any input appreciated.
Ok, problem solved. It was a faulty configuration of the webservice.
As you can see, the json response of the code attribute was:
"code": "0",
This format clearly stands for a string, therefor my guard with parsing into Int will fail.
To solve this, there are two possible ways I found.
Option 1: either change json response to:
"code": 0,
(value has no more surrounding quotes). This is probably the best solution as it fixes the wrong datatype of the webservice, but it requires to have full control over the codebase of the service.
Option 2: simply parse the json response into a String and after that, force-unwrap into an Int workes too. For this solution the webservice will remain unchanged.
guard let codeStr : String = "code" <~~ json else {
print("code unwrapping failed in guard")
return nil
}
self.code = Int(codeStr)!
Related
I parse JSON in my application and some of the JSONs have nil values which I handled. However, the app still shows me the error that JSON contains nil values. My code:
struct Response : Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let description : String
let url : String
let urlToImage : String
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let article = try JSONDecoder().decode(Response.self , from : data)
for i in 0...article.articles.count - 1 {
if type(of: article.articles[i].title) == NSNull.self {
beforeLoadNewsViewController.titleArray.append("")
} else {
beforeLoadNewsViewController.titleArray.append(article.articles[i].title)
}
if type(of : article.articles[i].urlToImage) == NSNull.self {
beforeLoadNewsViewController.newsImages.append(newsListViewController.newsImages[newsListViewController.newsIndex])
} else {
let url = URL(string: article.articles[i].urlToImage ?? "https://static.wixstatic.com/media/b77fe464cfc445da9003a5383a3e1acf.jpg")
let data = try? Data(contentsOf: url!)
if url != nil {
//make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
let img = UIImage(data: data!)
beforeLoadNewsViewController.newsImages.append(img!)
} else {
beforeLoadNewsViewController.newsImages.append(newsListViewController.newsImages[newsListViewController.newsIndex])
}
This is the error running the app:
FC91DEECC1631350EFA71C9C561D).description], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
Note:
This JSON works with other urls that don't have nil values.
and here is the json
{
"status": "ok",
"source": "entertainment-weekly",
"sortBy": "top",
"articles": [
{
"author": null,
"title": "Hollywood's Original Gone Girl",
"description": null,
"url": "http://mariemcdonald.ew.com/",
"urlToImage": null,
"publishedAt": null
},
{
"author": "Samantha Highfill",
"title": "‘Supernatural’: Jensen Ackles, Jared Padalecki say the boys aren’t going ‘full-mope’",
"description": "",
"url": "http://ew.com/tv/2017/10/18/supernatural-jensen-ackles-jared-padalecki-season-13/",
"urlToImage": "http://ewedit.files.wordpress.com/2017/10/supernatural-season-13-episode-1.jpg?crop=0px%2C0px%2C2700px%2C1417.5px&resize=1200%2C630",
"publishedAt": "2017-10-18T17:23:54Z"
},
{
The error is quite clear. JSONDecoder maps NSNull to nil so the decoder throws an error if it's going to decode a nil value to a non-optional type.
The solution is to declare all affected properties as optional.
let title: String
let description : String?
let url : String
let urlToImage : String?
or customize the decoder to replace nil with an empty string.
And because JSONDecoder maps NSNull to nil the check if ... == NSNull.self { is useless.
Edit:
Don't use ugly C-style index based loops use
let response = try JSONDecoder().decode(Response.self , from : data)
for article in response.articles {
beforeLoadNewsViewController.titleArray.append(article.title)
}
PS: But why for heaven's sake do you map the article instance to – apparently – separate arrays? You got the Article instances which contain everything related to one article respectively.
I am sorry, I cannot comment yet. But why don't you test for "null" the way, you did at the URLSession data check, with the guard-statement?
It would then look like:
guard let title = article.articles[i].title else {
beforeLoadNewsViewController.titleArray.append("")
return
}
beforeLoadNewsViewController.titleArray.append(title)
You try to parse a JSON structure here, in case the JSON structure would not fit to your needs, you still expect it to have the according attributes.
Could you also provide your JSON structure and how your decoding
try JSONDecoder().decode(Response.self , from : data)
looks like?
I would like to convert my json response to an array but it doesn't work and I got an error that says my items in the array are nil
func getcomments(){
RestApiManager.sharedInstance.getComments(TUTORIAL_ID: id){
response in
let comments = JSON(response)
for item in comments.array!{
let comment = Comment(memail: String(describing: item["email"]), mcomment: String(describing: item["comment"]), mcomment_date: String(describing: item["comment_date"]), manswer: String(describing: item["answer"]), manswer_date: String(describing: item["answer_date"]))
self.comments.append(comment)
}
}
}
it is my json response:
[{
"email": "-",
"comment": "\u0627\u0632 \u0627\u067e\u0644\u06cc\u06a9\u06cc\u0634\u0646 \u062e\u0648\u0628\u062a\u0648\u0646 \u0645\u0645\u0646\u0648\u0646\u0645",
"comment_date": "2017-07-15 19:30:00",
"answer": null,
"answer_date": null
},
{
"email": "S.M_Emamian#yahoo.com",
"comment": "salam",
"comment_date": "2017-07-11 19:30:00",
"answer": "\u062a\u0634\u06a9\u0631",
"answer_date": "2017-07-12 03:50:57"
}
]
I got nil error in this line:
unexpectedly found nil while unwrapping an Optional value
for item in comments.array!
According to your comment, response is actually a string. Therefore, you can't just create a JSON using init(_:). You need init(parseJSON:).
init(_:) will just create a JSON with just that string instead of a JSON object, which is obviously not what you want. init(parseJSON:) will actually parse your JSON string and allow you to access the different key value pairs.
func getcomments(){
RestApiManager.sharedInstance.getComments(TUTORIAL_ID: id){
response in
let comments = JSON(parseJSON: response)
It's easier if you decode it as an array of structures.
First, create the struct:
struct Comment: Codable {
var email: String
var comment: String
var comment_date: String
var answer: String
var answer_date: String
}
Then you can just call the JSON like that:
guard let url = Bundle.main.url(forResource: resource, withExtension: "json") else {
throw Errors.couldNotFindResource
}
data = try! JSONDecoder().decode([Comment].self, from: Data(contentsOf: url))
I have a very simple Swift code to retrieve JSON data. But somehow it doesn't work properly.
Alamofire.request("*URL*").validate().responseJSON { response in
print(response.result.value)
if response.result.isSuccess {
if let userList:[[String:Any]] = response.result.value as? [[String:Any]] {
for user:[String:Any] in userList {
if let userName = user["name"] as? String {
self._myList.append(User(name: userName, value: true))
}
}
}
self.tableView.reloadData()
} else {
print(response.result.error)
}
}
During the execution, I get this error message in the console :
Optional(Alamofire.AFError.responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
The print after calling Alamofire.request is showing "nil" in the console.
What I don't understand is it's working if I use .responseString instead of .responseJSON (but it shows only a String). I really need to use .responseJSON as I need to parse the result.
My JSON that appears on the web browser is very simple as well :
[{"name":"MrX","value":"0"}]
Any idea ?
Mika.
use this
import Alamofire
import SwiftyJSON
let baseURL: String = "http://baseURL.com"
Alamofire.request(baseURL)
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET")
print(response.result.error!)
return
}
if let value = response.result.value {
let json = JSON(value) // JSON comes from SwiftyJSON
print(json) // to see the JSON response
let userName = json["name"].array // .array comes from SwiftyJSON
for items in userName! {
self._myList.append(User(name: items, value: true))
}
DispatchQueue.main.async {
self.tableView?.reloadData()
}
}
}
and take the .validate() out, if you take that out you will see more detailed error description. Hope that helps.
I am working on an iOS application written in Swift which parse a lot of JSON files.
The JSON structures are sophisticated and I would to test them before to map JSON to object.
For example test if the key 'users' exists and for each user the structure('name', 'first', 'last').
{
"users": [
{
"name": {
"first": "emmi",
"last": "wiita"
}
},
{
"name": {
"first": "erdi",
"last": "nielen"
}
},
{
"name": {
"first": "daniel",
"last": "beck"
}
}
]
}
Are there any good way to do this?
Thanks.
The only way to accomplish that is really opening the JSON file and testing each property.
A good news is that since Swift 2.0 you can use guard to test if you can assign a valid value to a var or let, so you can create a function as follows:
func isValidJSON(data: NSData) -> Bool {
json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
// if "users" exists on the JSON
// AND we can cast it to an array of dictionaries
guard let users = json["users"] as [[String: AnyObject]] else {
return false
}
for user in users {
guard let name = user["name"] as [[String: AnyObject]]
firstName = name["first"] as String,
lastName = name["last"] as String else {
return false
}
}
// valid JSON
return true
}
A best practice would be also to implement the use of Exceptions instead returning false in each guard statement.
Thank you for your post #Felipe Plets. I found a good way to test JSON file.
I have implemented an enum ErrorType(Exception):
/**
Enumeration describing possible errors that occur while parsing
a message from JSON file.
*/
enum ReceiverError: ErrorType {
/**
Error trigge when the key is missed or the type.
- Parameter key: Key missed.
*/
case MissingKeyOrType(key: String)
}
then I can test all the JSON file like this:
func isValidJSON(message: [String: AnyObject]) -> throws {
guard let sosId = message["id"] as? String
else { throw ReceiverError.MissingKeyOrType(key: "sos - id") }
guard let text = message["text"] as? String
else { throw ReceiverError.MissingKeyOrType(key: "text")
}
let json = ... Get JSON
do {
try isValidJSON(json)
} catch CustomReceiver.ReceiverError.MissingKeyOrType(let key) {
print("Error: Missing key or type [key: \(key)].")
}
I am working on an iOS weather app and need some help getting some values with JSON. I am trying to pull the value of id in the weather object. I am getting a value of nil when I debug. Could someone please help me with the logic? Here is the JSON request and Swift code.
{
"coord":{
"lon":138.93,
"lat":34.97
},
"weather":[
{
"id":800,
"main":"Clear",
"description":"clear sky",
"icon":"01n"
}
],
"base":"cmc stations",
"main":{
"temp":292.181,
"pressure":1005.21,
"humidity":100,
"temp_min":292.181,
"temp_max":292.181,
"sea_level":1014.59,
"grnd_level":1005.21
},
"wind":{
"speed":3.41,
"deg":78.0005
},
"clouds":{
"all":0
},
"dt":1464801034,
"sys":{
"message":0.003,
"country":"JP",
"sunrise":1464723086,
"sunset":1464774799
},
"id":1851632,
"name":"Shuzenji",
"cod":200
}
Here is my Swift snippet:
let requestURL: NSURL = NSURL(string: "http://api.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=6361e893fa064b1bfeaca686cd0929cc")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("JSON Downloaded Sucessfully.")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let today = json["weather"] as? [[String: AnyObject]] {
//this is pulling 4 key value pairs
for weather in today {
let id = weather["id"] as? String
self.trumpDescription.text=id;
print(id)
}
}
}
catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
}
The problem lies in your cast to a string. If you do not cast the id as a String, it will print "800" as an Int.
When you are grabbing the id from the json and converting it to a string, it reads the string and includes the new line character.
I played around in the playground
So you are correctly getting to the spot in the json, but you need to unwrap your optional.
You can also use the nil coalescing operator to unwrap optionals so you don't have to have a bunch of if lets:
let id = weather["id"] as? String ?? ""
Which will either set id to the value or to "" if it doesn't exist.
try this in your code:
let id = weather["id"]?.stringValue
instead of this:
let id = weather["id"] as? String
And see the magic!
Edit for explanation:
As this answer worked for you, let me tell you why it did.
The id is being sent as integer from the server side. When you do weather["id"] it returns object of type AnyObject?. When you do weather["id"] as? String the casting fails thus you were getting nil.