I am trying to parse JSON with the following code:
func ltchandler(response: NSURLResponse!, data : NSData!, error : NSError!) { //Is passed the results of a NSURLRequest
if ((error) != nil) {
//Error Handling Stuff
} else {
if (NSString(data:data, encoding:NSUTF8StringEncoding) == "") {
//Error Handling Stuff
} else {
var data = NSData(data: data);
// Define JSON string
var JSONString = "\(data)"
// Get NSData using string
if let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
// Parse JSONData into JSON object
var parsingError: NSError?
if let JSONObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parsingError) as? [String: AnyObject] {
// If the parsing was successful grab the rate object
var rateObject: Double! = JSONObject["price"]?.doubleValue
// Make sure the rate object is the expected type
if let rate = rateObject as? Double! { // THIS IS NOT WORKING!!!
//Do stuff with data
} else {
println("Parsing Issue")
}
}
}
}
}
}
The line marked THIS IS NOT WORKING!!! is not being called.
From what I can tell, it cannot cast rateObject as a double - why not? It is not showing any errors.
To clarify, the expected behavior is that a double is created from the JSON object.
To strictly answer your question have you tried printing the rateObject. Also why are you casting to Double! rather than just Double in the problematic line?
Personally I don't use ! in almost all cases. You are better off using either non-optionals or proper optionals.
In the relevent section I would write:
// Make sure the rate object is the expected type
if let rate = JSONObject["price"]?.doubleValue {
//Do stuff with rate
} else {
print("Parsing Issue")
}
Of course if the JSONObject["price"] is not something with a doubleValue method or the method returns nil you will end up with nil and the else case being taken.
the code worked for me, try this code:
// if the value equals nil or any String, the instruction abort the if
// SWIFT 2.0 in xcode beta 5
if let rate = Double((JSONObject["price"] as? String)!){
// insert you code here
} else {
print("error message")
}
Related
I'm having an issue with displaying a deserialised JSON object in a view. The problem seems to be that my view is trying to unwrap a value from a published variable before anything is assigned to it by the function that gets the JSON object.
Here is the code that calls the api
class ViewModel : ObservableObject {
#Published var posts : first?
init(subReddit : String){
fetch(sub: subReddit)
}
func fetch(sub : String) {
guard let url = URL(string: "https://www.reddit.com/r/" + sub + "/top.json?t=day") else {
return
}
let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data, error == nil else {return}
do{
let retVal = try JSONDecoder().decode(first.self, from:data)
DispatchQueue.main.async {
self?.posts = retVal
}
}
catch{
print(error)
}
}
task.resume()
}
}
and here is the code for my view:
struct SubRedditView: View {
#StateObject var viewModel = ViewModel(subReddit: "all")
var body: some View {
NavigationView{
List{
ForEach((viewModel.posts?.data.children)!) {post in//at runtime I get a nil unwrap error here
Text(post.data.title)
Text(post.data.url_overridden_by_dest ?? "No Value")
}
}
.navigationTitle("Posts")
}
}
}
If only the object representing the children is relevant declare the published object as empty array of this type
#Published var posts = [TypeOfChildren]()
Then assign the children to the array
self?.posts = retVal.data.children
This makes the code in the view easier and safe.
ForEach(viewModel.posts) { post in
Text(post.title)
Text(post.url_overridden_by_dest ?? "No Value")
My code checks if code: "this data" is not empty, how can I check also if code itself exists. Some responses might give me almost an empty JSON with just time stamp. So the var code won't be there.
Or is there a better way altogether to do this? as My JSON is
"Variable Based on text input" which leads to "code" which might not be there which will have "some info" or ""
Top of script:
struct Variables: Decodable {
var code: String
}
typealias DecoderX = [String: Variables]
Previous function sets inputs from user text which are cross checked with the database so GetInfo will only be called if UserDefault inputs are set.
func GetInfo() {
let Input1 = UserDefaults.standard.string(forKey: “UserInput1”) ?? “”
let Input2 = UserDefaults.standard.string(forKey: “UserInput2”) ?? “”
let Input3 = UserDefaults.standard.string(forKey: “UserInput3”) ?? “”
print(“Input Check 1: \(Input1) \(Input2) \(Input3)”)
// URL INFO With API key hidden
let jsonTask = URLSession.shared.dataTask(with: requestURL) { data, response, error in
if response == response {
if let data = data, let body = String(data: data, encoding: .utf8) {
do {
let json = try? decoder.decode(DeocderX.self, from: data);
DispatchQueue.main.async {
print(“Input Check 2: \(json![Input1]!.code) \(json![Input2]!.code) \(json![Input3]!.code)”)
if json?[Input1]?.code != nil {
print("Good Information 1")
} else {
print("Found Nothing 1")
}
if json?[Input2]?.code != nil {
print("Good Information 2")
} else {
print("Found Nothing 2")
}
if json?[Input3]?.code != nil {
print("Good Information 3")
} else {
print("Found Nothing 3")
}
}
// rest of code not applicable
You can use SwiftyJSON library. You can find it at this link
if let json = try? JSON(data: response.data!){
if json["yourKey"]["otherKey"].exists() {
print("exists")
}
}
Hope it helps...
I am fairly new to parsing json data and I am attempting to parse some json data from an rss feed generator and I am running into a problem where I can successfully print the data I am getting but I can't save the data to an object.
I have looked through tutorials that used decodables/codables mostly but I was able to use the urlSession and jsonSerialization objects for what I needed just fine.
class JSONSongs {
// initialize song array...
var songArray: [Song] = []
func getSongs() {
let jsonSongUrl = "https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/50/explicit.json"
let songUrl = URL(string: jsonSongUrl) // convert string to usable url
// start url session task with apple music api url...
// we get some data(hopefully), a response code and an error(hoepfully not)
let songTask = URLSession.shared.dataTask(with: songUrl!) { (data, response, error) in
// checking for an error
if error != nil {
print(Error.self)
print(error?.localizedDescription)
return
} else {
// lets store our data in a variable
if let content = data {
do {
// taking the json data and converting it so we can make objects
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)
//print(json) // making sure data is present
// checking to see if our json data is there
guard let jsonOne = json as? [String: Any] else {
print("invalid operation!")
return
}
// accessing top root of the json file
if let feed = jsonOne["feed"] as? [String: Any] {
//print("it worked") // testing
// accessing the results array where the albums are stored
// there are arrays in the nested json data so we need the double brackets to access them
if let result = feed["results"] as? [[String: Any]]{
for item in result {
// attempting to store data in Song object, this is where problems appear
if let songName = (item["name"] as AnyObject? as? String),
let artistName = (item["artistName"] as AnyObject? as? String),
let coverArt = (item["artworkUrl100"] as AnyObject? as? String),
let artistPage = (item["artistUrl"] as AnyObject? as? String) {
self.songArray.append(Song(songName: songName, artistName: artistName, coverArt: coverArt, artistPage: artistPage))
// printing the data to the console works here but I can't save the data to an object
}
}
}
}
} catch {
print(error.localizedDescription)
print(Error.self)
return
}
}
}
}
songTask.resume()
}
}
All I get is either nil when I try and print a string value or 0 when I try and count the number of objects that are present in the songArray array
Basically your code is correct and should work, however this is a version using Decodable.
The songs property will contain the song data
struct Root : Decodable {
let feed : Feed
}
struct Feed : Decodable {
let results : [Song]
}
struct Song : Decodable {
let name, artistName : String
let artworkUrl100, artistUrl : URL
}
class JSONSongs {
var songs = [Song]()
func getSongs() {
let jsonSongUrl = "https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/50/explicit.json"
let songUrl = URL(string: jsonSongUrl) // convert string to usable url
// start url session task with apple music api url...
// we get some data(hopefully), a response code and an error(hoepfully not)
let songTask = URLSession.shared.dataTask(with: songUrl!) { [weak self] data, _, error in
// checking for an error
if let error = error { print(error); return }
do {
// taking the json data and converting it so we can make objects
let result = try JSONDecoder().decode(Root.self, from: data!)
self?.songs = result.feed.results
print(self?.songs)
} catch {
print(error)
}
}
songTask.resume()
}
}
I have the following swift3 code. The JSON can return a NSNull value for the $0[2] value.
struct Player3 {
let name : String
var score : String
let avatar : String
}
class HistoricLeagueVC: UITableViewController {
var players = [Player3]()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://str8red.com/jsonoverallleaderboard/1025/"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("there was an error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[String]]
self.players = parsedData.map { Player3(name: $0[0], score: $0[1], avatar: $0[2]) }
print(self.players.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error as NSError {
print(error)
}
}
}.resume()
}
I have tried to convert it to an empty string without success. I have also tried to set a default string such as "https://str8red.com/static/no_profile_picture.png" which ideally is what I would like to do.
The error in the terminal states Could not cast value of type 'NSNull' (0xed1c78) to 'NSString' (0x57b6b8).
Any help would be appreciated.
Do not cast to [[String]] then:
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[Any]]
self.players = parsedData.map { Player3(
name: ($0[0] as? String) ?? "",
score: ($0[1] as? String) ?? "",
avatar: $0[2] as? String, // keeping `nil` here
)}
You should probably even as! [[Any]] do in a safer manner (using as?) but the above will work.
Your code will crash brutally if anything goes wrong with your input data. Ask your boss if he or she is Ok with that. I wouldn't. Here's how you will crash: If data == nil (but it shouldn't be if error == nil). You catch the problem that data cannot be parsed without crashing. You crash if data doesn't parse to an array, you crash if the elements of the array are not all arrays, you crash if the elements inside the inner arrays are not all Strings, and you crash if any of the inner arrays contain two or fewer strings. I'd say that is pretty unacceptable.
My strategy would be: If the response cannot be parsed or is not an array of arrays, then ignore it, leaving self.players unchanged. If there is an array of arrays, reset self.players, then add a player for each array that contains at least three items, of which the first two must be strings.
guard data != nil else { return }
guard let parsed = try? NSJSONSerialization(with: data!) else { return }
guard let arrays = parsed as? [[Any]] else { return }
self.players.removeAll()
for array in arrays {
guard array.count >= 3,
let name = array [0] as? String,
let score = array [1] as? String else { continue }
let avatar = array [2] as? String ?? ""
self.players.append(Player3 (name: name, score: score, avatar: avatar))
}
It's a good idea to move something like this into a separate method. Add whatever error handling or logging you feel is appropriate.
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.