I googled a lot, pretty much copied code i found online from tutorials to simply parse a json String in Swift to useable objects.
Code:
func parseJson(json: String) -> [AnyObject] {
let data = json.dataUsingEncoding(NSUTF8StringEncoding)
do {
if let array = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? [AnyObject] {
return array
}
}
catch {
// Error hanndling here
}
return [AnyObject]()
}
Json String im trying to parse:
"response":{"loggedIn":false,"message":"Some errormessage here"}}
What happens:
The program won't jump into the if let array = ... It stops there since it can't parse the string to json (or AnyObject) and will simply go to return AnyObject.
Why does this happen and how do i fix it?
Adjust your code a little to allow for better debugging:
func parseJson(json: String) -> [AnyObject] {
let data = json.dataUsingEncoding(NSUTF8StringEncoding)
do {
let parsed = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
if let array = parsed as? [AnyObject] {
return array
}
}
catch {
print(error)
}
return [AnyObject]()
}
Two changes there:
Printing any error caught.
Doing JSONObjectWithData and the as? conversion in two separate steps.
Pasting this in a playground quickly reveals an error being caught: "JSON text did not start with array or object and option to allow fragments not set." Your JSON fragment is missing the opening {.
Once that problem is fixed, you’ll see that parsed gets set, but the subsequent if let array = parsed as? [AnyObject] falls through. That’s because your top-level element is a dictionary, not an array, so casting to [AnyObject] fails.
Related
I decide some JSON and try to typecast it to a dictionary of String: classy and it fails. I have found that often the reason I have trouble doing something is because of a misunderstanding of how Swift works, so here is what I want to happen. Feel free to tell me that I am doing it wrong and if I do it this way all will be wonderful.
I want my data to live between runs of the app so I have to save the data to storage between runs. I have an object, data and associated code, and I have places where changes I make to a copy should reflect back to the original so it is a class. I have a bunch of these objects and most of the time I pick the one I want based on an id that is an integer. An array is not good since it would be a sparse array cause come ids are not used. I came up with a dictionary with a key of the id and data of the structure. I turned the key from an Int to a String, by changing the Int id to a String, cause converting a dictionary to JSON is MUCH easier for a key that is a string. I save the JSON string. When the app starts again I read the string in and convert the JSON string to Any. Then I typecast the result to the desired dictionary. This is where it fails. The cast does not work. In my Googling the samples I found said this should work.
Here is my code:
class Y: Codable, Hashable {
var a: String = "c"
static func ==(lhs: Y, rhs: Y) -> Bool {
return lhs.a == rhs.a
}
func hash(into hasher: inout Hasher) {
hasher.combine(a)
}
}
struct ContentView: View {
var body: some View {
VStack {
Button ("Error") {
var y = Y()
var yDict = [String: Y]()
yDict["0"] = y
do {
let encodedData = try JSONEncoder().encode(yDict)
let jsonString = String(data: encodedData, encoding: .utf8)
let decoded = try JSONSerialization.jsonObject(with: encodedData, options: [])
if let yyDictDec = decoded as? [String:Y] {
print("yDict after decide")
print (yyDictDec)
}
} catch {
print(error.localizedDescription)
}
print("x")
}
}
}
}
In this code the if yyDictDec = is failing, I think, cause the prints after it never happen. I can cast it as [String, Any] but I really need it to be my class.
My problem is in the convert JSON back to the dictionary. I feel I am missing something fairly simple.
Don´t use JSONSerialization use JsonDecoder and decode it to the the type it was before encoding. e.g.:
let decoded = try JSONDecoder().decode([String: Y].self, from: encodedData)
NOTE: Somewhat similar questions have been already asked. And yet none of them provides how to solve this seemingly simple task. So I hope it gets resolved here once and for all.
MY PROBLEM:
I am receiving this nested JSON object:
print("type(of: JSON) \( type(of: JSON))") //__NSDictionaryI
completion(true, nil, JSON as? [String: Any], nil)
Alamofire module converts it, as you see, to a Dictionary.
I have been trying for hours on end to access the nested values with different methods (Something, I thought that should be straightforward compared to JavaScript), inside this Dictionary but I couldn't find a single way that works.
So except for the high-level values, I couldn't access anything else:
for (key,movieData) in moviesData! { // moviesData is the JSON dictionary object
// Do some logic
}
So is there anyway to easily manipulate/access the received JSON data?
This is a far from being an ideal solution but it works. So here it is:
for (key,movieData) in moviesData! {
if let nestedDictionary = moviesData![key] as? [String: Any] {
print("nestedDictionary: ",nestedDictionary) // logs the result part of the JSON object
// Trying to access nested values
for (key,value) in nestedDictionary {
print("nestedDictionary.key: ",key)
print("nestedDictionary.type(of:value): ",type(of:value) )
print("nestedDictionary.value: ",value)
// Testing if sections is an array of dictionaries
if let arrayOfDictionaries = nestedDictionary[key] as? [[String: Any]] {
print("nestedDictionary: ",nestedDictionary) // logs the result part of the JSON object
// Trying to access array items which are sections
for item in arrayOfDictionaries {
print("arrayOfDictionaries.item: ",item)
}
}
}
}
}
It is based on the tip mentioned here:
if let dictionary = jsonWithObjectRoot as? [String: Any] {
if let number = dictionary["someKey"] as? Double {
// access individual value in dictionary
}
for (key, value) in dictionary {
// access all key / value pairs in dictionary
}
if let nestedDictionary = dictionary["anotherKey"] as? [String: Any] {
// access nested dictionary values by key
}
}
The key idea is to check if there's a nested dictionary or a nested array inside the json and then iterate accordingly.
Having an issue with my program. I would appreciate it if someone could help out. I have tried for weeks to parse the JSON files fetched from the iTunes API
(itunes.apple.com/search?term=song+you+want+to+search&entity=songTrack).
However, my answers are never displayed on my tableview and an error always shows up in the terminal:
"2017-11-14 17:25:28.809190+0100 Itunes Learning[32409:6240818] [MC] Lazy loading NSBundle MobileCoreServices.framework
2017-11-14 17:25:28.810264+0100 Itunes Learning[32409:6240818] [MC] Loaded MobileCoreServices.framework
2017-11-14 17:25:28.823734+0100 Itunes Learning[32409:6240818] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/cyprianzander/Library/Developer/CoreSimulator/Devices/D52FD9D5-B6E4-4CE0-99E4-6E0EE15A680D/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
2017-11-14 17:25:29.875534+0100 Itunes Learning[32409:6240900] Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
(lldb) "
This is approximately how the JSON file is set up:
{“resultCount” : 50, “results”: [ {“trackName”:”name”, ”artistName”:”name2”}, {“trackName”:”name3”, “artistName”:”name4”} ] }
(An array of objects inside an array - meaning the first object is on the far outside).
I have tried my function with another API, which did work. I have the feeling that the main reason as to why this happens, is because the iTunes API JSON file is very complex. It is an assortment of very long objects inside an array, which is inside a smaller list of objects. However, the other one was only and array of objects.
Here is my code: (I have noticed that the problem occurs while parsing the data I need. The only thing I need to know is how to properly unwrap my JSON file)
func parseData(searchTerm: String) {
fetchedSong = []
let itunesSearchTerm = searchTerm.replacingOccurrences(of: " ", with: "+", options: .caseInsensitive, range: nil)
let escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
let urlString = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&entity=song"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
// If there is an error in the web request, print it to the console
print(error)
return
}
else {
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
print(fetchedData)
for eachFetchedSong in fetchedData {
let eachSong = eachFetchedSong as! [String: Any]
let song = eachSong["trackName"] as! String
let artist = eachSong["artistName"] as! String
self.fetchedSong.append(songs(song: song, artist : artist))
}
self.SongTableView.reloadData()
}
catch {
print("An error occured while decoding the JSON object")
}
}
}.resume()
}
If anyone could help me, I would be extremely happy, especially because I have been stuck with this for three weeks, continuously trying different techniques (this one seemed the most successful).
Your JSON data is not an array. It is a dictionary with two key/value pairs. The first is the key "resultCount" with a value of 50, and the second is the key "results" with an array as its value.
Never use as! when parsing JSON, since this will crash your app if you get an unexpected result. Don't use .mutableLeaves unless you can explain to us what it does and why you need it. Don't use NSArray in your Swift code.
Handling one error and crashing on others is pointless. I'd write
if let fetchedDict = try? JSONSerialization(...) as? [String:Any],
let fetchedArray = fetchedDict ["results"] as? [[String:Any]] {
for dict in fetchedArray {
if let song = dict ["trackName"] as? String,
let artist = dict ["artistName"] as? String {
...
}
}
}
I'm Having trouble with JSON and Swift 2.
I'm getting this Array from the server
[{"KidName":"Jacob","KidId":1,"GardenID":0},
{"KidName":"Sarah","KidId":2,"GardenID":0},
{"KidName":"Odel","KidId":3,"GardenID":0}]
I'm familiar with JSON and I know it's not the recommended way to get a JSON, since it's supposed to be something like
{"someArray":[{"KidName":"Jacob","KidId":1,"gardenID":0}, .....
So my first question is it possible to run over the first JSON I've post and get the KidName number without editing the JSON and Add to it a JSON OBJECT to hold the array ?
my second question is really with Swift 2, how can I get the KidName (after I've edited the JSON to have an holder for the array)?
this is my code... (please read the Notes I've added)
BTW, I'm familiar with SwiftyJSON as well...
// Method I've build to get the JSON from Server, the Data is the JSON
sendGetRequest { (response, data ) -> Void in
// need to convert data to String So I can add it an holder
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
/**
after editing the str, i'm Having a valid JSON, let's call it fixedJSON
*/
let fixedJSON = "{\"kidsArray\":\(dropLast)}"
// Now I'm converting it to data back again
let jsonTodata = fixedJSON.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// After Having the data, I need to convert it to JSON Format
do{
let dataToJson = try NSJSONSerialization.JSONObjectWithData(jsonTodata, options: []) as! [String:AnyObject]
//Here I'm getting the KidID
if let kidID = jsonSe["kidsArray"]![0]["KidId"]!!.integerValue {
print("kidID in first index is: \(kidID)\n")
}
//NOW trying to get the KidName which not working
if let kidname = jsonSe["kidsArray"]![0]["KidName"]!!.stringValue {
print("KidName is \(kidname)\n")
}
}
So as you can see, I'm not able to get the KidName.
Any Help Would be Appreciate.
You can use the following function to get the 'someArray' array and then use this getStringFromJSON function to get the 'KidName' value.
func getArrayFromJSON(data: NSDictionary, key: String) -> NSArray {
if let info = data[key] as? NSArray {
return info
}
else {
return []
}
}
let someArray = self.getArrayFromJSON(YourJSONArray as! NSDictionary, key: "someArray")
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String {
return info
}
return ""
}
let KidName = self.getStringFromJSON(someArray as! NSDictionary, key: "KidName")
Hope this might be useful to you.
I'm new to Swift - trying to read a JSON file from a URL. My attempt below.
The JSON looks valid - I tested it with JSONLint but it keeps crashing.
Thoughts?
func getRemoteJsonFile() -> NSDictionary {
//Create a new url
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
//check if its nil
if let actualRemoteUrl = remoteUrl {
//try to get the data
let filedata:NSData? = NSData(contentsOfURL: actualRemoteUrl)
//check if its nil
if let actualFileData = filedata {
//parse out the dictionaries
let jsonDict = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
return jsonDict
}
}
return NSDictionary()
}
This took me a second to figure out, so I don't blame you for missing it.
The JSON you linked to is minified, so it's difficult to see the structure. Let's take a look at (a fragment of) it after piping it through a prettifier:
[
{
"PlayerId":2501863,
"PlayerName":"Peyton Manning",
"PlayerTeam":"DEN",
"PlayerPosition":"QB",
"PlayerPassingYards":4727,
"PlayerPassingTDs":39,
"PlayerInterceptions":15,
"PlayerRushingYards":-24,
"PlayerRushingTDs":0,
"PlayerReceivingYards":0,
"PlayerReceivingTDs":0,
"PlayerReturnYards":0,
"PlayerReturnTDs":0,
"PlayerFumbleTDs":0,
"PlayerTwoPointConversions":2,
"PlayerFumblesLost":2,
"PlayerTeamLogo":"http://i.nflcdn.com/static/site/7.0/img/logos/teams-gloss-81x54/den.png"
}
]
Huh. It's encased in brackets, which means that it's an array.
It's an array, so you can't cast it as an NSDictionary. Instead, you could cast it as an NSArray, but why not use native Swift types?
Well, if you don't like types, you're about to find out, but I still think that this is a better way, because it forces you to think about the data you're parsing.
So we have the first part of our type definition for this function; it's an array ([]). What components is our array made up of? We could go with a simple NSDictionary, but we're doing full native types here, so let's use a native Swift dictionary.
To do that, we have to know the types of the dictionary (the syntax for a native dictionary type is [KeyType: ValueType]). Examining the JSON shows that all of the keys are Strings, but the values are of varying types, so we can use AnyObject.
That gives us a dictionary type of [String: AnyObject], and our entire JSON is an array of that, so the final type is [[String: AnyObject]] (wow).
Now that we have the proper type, we can modify the function you're using to parse the JSON a bit.
First of all, let's use our new type for the return and cast values. Then, let's make the return type optional in case something goes wrong and add an error variable to document that.
A cleaned up function would look something like this:
func getData() -> [[String: AnyObject]]? {
let data: NSData? = NSData(contentsOfURL: NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")!)
if let req: NSData = data {
var error: NSError?
if let JSON: [[String: AnyObject]] = NSJSONSerialization.JSONObjectWithData(req, options: NSJSONReadingOptions.AllowFragments, error: &error) as? [[String: AnyObject]] {
return JSON
}
}
return nil
}
That's it!
We can now call the function and extract values from our [[String: AnyObject]] (again, wow) like this:
if let data: [[String: AnyObject]] = getData() {
println(data[0]["PlayerName"]!) // Peyton Manning
}
Update your code with this:
func getRemoteJsonFile() -> [NSDictionary] {
// Create a new URL
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
let urlString:String = "\(remoteUrl)"
// Check if it's nil
if let actualRemoteUrl = remoteUrl {
// Try to get the data
let fileData:NSData? = NSData(contentsOfURL: actualRemoteUrl)
// Check if it's nil
if let actualFileData = fileData {
// Parse out the dictionaries
let arrayOfDictionaries:[NSDictionary]? = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.MutableContainers, error: nil) as [NSDictionary]?
if let actualArrayOfDictionaries = arrayOfDictionaries {
// Successfully parsed out array of dictionaries
return actualArrayOfDictionaries
}
}
}
return [NSDictionary]()
}
This is working fine for me.