Using Xcode 6.4 and programming in swift.
I am typing a program that should read in JSON from a URL. A sample of the JSON can be found at this URL (https://itunes.apple.com/us/rss/topmovies/limit=2/json) and Ive been using this site to parse the JSON in order to read it better (http://json.parser.online.fr/).
Now I need to work through the levels of the JSON in order to get to
the actual movie names and image URL's but I am lost at what kind of variable entryDictionary should be. I was thinking it should be an array of dictionaries, and this compiles, but the output of entryDictionary in the console is sloppy looking, starting with Optionl( and not entry{ as it should. And when I go to loop through entryDictionary, I get an error saying entry does not have a subscript of type AnyObject.
So I am asking how I retrieve the im:name fields and im:image from the JSON.
func downloadDataFromURLString(urlString: String) {
//Downloaded data and is now stored in data. Took code out because
//irrelevant to my problem at this point. Data variable has correct
//JSON, now I am trying to parse it.
} else { //download suceeded, time to parse
var error: NSError? = nil
var names = [String]()
if let rootDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? [String: AnyObject] {
let feedDictionary = rootDictionary["feed"] as! [String: AnyObject]
let entryDictionary: AnyObject? = feedDictionary["entry"]
println(entryDictionary) //For debugging
//for entry in entryDictionary as! NSArray {
// let name = entryDictionary["name"]
// let image = entryDictionary["image"]
// let movie = Movie(name: name!, image: image!)
// weakSelf!.movies.append(movie)
//}
here is a blueprint of the JSON
"feed":{
"author":{},
"entry":[
{
"im:name":{
"label":"Deadpool"
},
"im:image":[],
"summary":{},
"im:price":{},
"im:contentType":{},
"rights":{},
"title":{},
"link":[],
"id":{},
"im:artist":{},
"category":{},
"im:releaseDate":{}
AnyObject is indeed not subscriptable (you're trying to subscript a variable whose type is AnyObject? with ["feed"]). You should also avoid casting to Cocoa container types like NSArray and NSDictionary whenever you can. Here's an example of how you might get the labels out of the entries array's names array:
import Foundation
func labels(feedDictionary:[String:AnyObject]) -> [String] {
guard let entries = feedDictionary["entry"] as? [String:AnyObject] else {
return []
}
return entries.flatMap { (key:String, value:AnyObject) -> String? in
guard key == "im:name" else {
return nil
}
guard let name = value as? [String:String] else {
return nil
}
return name["label"]
}
}
I'd however advise against using NSJSONSerialization on its own in Swift for anything but the simplest case, as you end up casting and wrapping optionals until the cows come home.
There are good 3rd party libraries such as Freddy and SwiftyJSON which apply Swift language features to accomplish a very convenient JSON (de)serialization experience.
For instance with Freddy you could express your problem in the following style:
let json = try JSON(data: data)
json.decode("feed", type:Feed.self)
struct Feed: JSONDecodable {
let entries:[Entry]
init(json: JSON) throws {
self.entries = try json.arrayOf("entry", type:Entry.self)
}
}
struct Entry:JSONDecodable {
let name:IMName
init(json: JSON) throws {
self.name = try json.decode("im:name", type:IMName.self)
}
}
struct IMName:JSONDecodable {
let label:String
init(json: JSON) throws {
self.label = try json.string("label")
}
}
Related
I am developing a swift 5 project using Freddy FrameWork (for parsing). I have a requirement like when user search for the zipcode in autocomplete it will display the zipcodes from Array. But my code is getting crash.I am retrieving the zipcode from "JSON"(using Freddy). using the below code
struct FreddyJson: JSONDecodable {
let zipCodes: [JSON]
}
init(json: JSON) throws {
do{
let zipCodes = try? json.getArray(at: "WarningConfigurationUS", "ZipCode")
self.zipCodes = zipCodes!
}catch{
print(error)
}
}
After that I am calling this in ViewController.swift file like as below
func FreddyDataParsing()
{
let data = DataService.shared.readCustomizerMappingJson()
guard let freddy = try? FreddyJson(json: data) else {return}
self.autoCompletionPossibilities = freddy.zipCodes // I am getting an error "Cannot assign value of type '[JSON]' to type '[NSString]'"
}
The below code is fo getting the autosearch result
func getAutocompleteSuggestions(userText: String) -> [String]{
var possibleMatches: [String] = []
for item in autoCompletionPossibilities { //2
let myString:NSString! = item as NSString. //Here also I am getting the same error
let substringRange :NSRange! = myString.range(of: userText)
if (substringRange.location == 0)
{
possibleMatches.append(item as String)
}
}
return possibleMatches
}
Can anyone help me to resolve the above issue. Thanks in advance.
so I have a json object that looks like this.
{
geometry = {
location = {
lat = "51.5194133";
lng = "-0.1269566";
};
};
id = ad6aaec7b7b0fa2c97a127c24845d76135e760ae;
"place_id" = ChIJB9OTMDIbdkgRp0JWbQGZsS8;
reference = "CmRRAAAAiC-ErdlAvz74Drejj2mAAh6Plr46e889a3Uv6CrRXFqNtVatoFsOTarDH0KU8KCkWoN--QGv01RSjLBZblbrAHNPGDVdiXikedid0vKMVM_LQtXstrSQFt4s-Z-Wi-1AEhDJRWc9bdWpKHPPOrt7QGTqGhSJgMPENn_wSGbprGYLv52csv5BtQ";
}
I was wondering how you can extract the information at different levels for example the location object is an object within the geometry objet and I want to extract lat from there how can I do this?
I can print out the location object like:
let setOne = jsonResult["results"]! as! NSArray
let y = setOne[0] as? [String: AnyObject]
print(y!)
print((y!["geometry"]!["location"]!)!["lat"])
but when I try to do:
print((y!["geometry"]!["location"]!)!["lat"])
it gives me the error: Type 'Any' has no subscript members
Perhaps the easiest way to do this is to use JSONDecoder to decode your JSON directly into structs.
You first need to define structs that match your JSON structure, like so:
struct Place: Codable {
let geometry: Geometry
let id: String
let place_id: String
let reference: String
}
struct Geometry: Codable {
let location: Location
}
struct Location: Codable {
let lat: String
let lng: String
}
Now, assuming that jsonResult["results"] is in fact an NSArray, you first need to convert it to Data and then use the JSONDecoder to decode the JSON into the structs:
if let data = try? JSONSerialization.data(withJSONObject: jsonResult["results"], options: []) {
if let places = try? JSONDecoder().decode(Array<Place>.self, from: data) {
print(places[0].geometry.location.lat) //prints "51.5194133"
}
}
The advantage of this approach is that you write much less code to do the actual decoding.
Note that if any of the JSON elements might be missing, you should declare the corresponding struct let property as an optional. For example, if reference might not always be present, you would code it as:
let reference: String?
Anything that is not optional must be present in the JSON, or the decode will fail. This is why you want to use try? when decoding, so that your app does not crash.
Lets say you have an array of results data, you don't need to use NSArray since you are using Swift so here you can start.
In Swift you need to specify the type, And also try not to use AnyObject unless you really need it, Always provide the type and thats what the error is saying:
// make sure that results exist
if let resultArray = jsonResult["results"] as? [Dictionary<String,Any>] {
// loop through the result array
for object in resultArray {
// get geometry from the object in the array
if let geometry = object["geometry"] as? Dictionary<String,Any>{
// make sure it has location
if let location = geometry["location"] as? Dictionary<String,Any>{
// now get the lat or lng safely.
let lat = location["lat"] as? String ?? "0.0"
let lng = location["lng"] as? String ?? "0.0"
}
}
}
}
Note: Never use force-unwrap ! because your app will crash if something went wrong or that specific object is not found, make sure to use guard let or if let at least, although this is manual parsing you can look into Swift 4 new Encoding, Decoding and Serialization in Swift 4.
Considering you have an array of data as results (jsonResult), start accessing your array and identify the type of each parameter.
if let resultArray = jsonResult["results"] as? NSArray {
for aResult in resultArray {
//using "if let" simply means, in this case, if "geometry" is indeed a dictionary it will execute the statement/s inside this "if" block.
if let geometry = aResult["geometry"] as? NSDictionary {
if let location = geometry["location"] as? NSDictionary {
if let latValue = location["lat"] as? String {
//This will only be executed if the value is String
print(latValue)
}
if let lngValue = location["lng"] as? String {
print(lngValue)
}
}
}
}
}
What I'm trying to do is retrieve json data(which is in array format) and check to see if my local array already contains the data, if it does move on to the next value in the JSON data until their is a value that the array doesn't contain then append it to the array. This data in the array must be in order. I'm attempting to do this now but get the error:
Type 'ResultsGenrePosters' does not conform to protocol 'Sequence'
This is what it looks like:
public struct ResultsGenrePosters: Decodable {
public let results : [GenrePosters]?
public init?(json: JSON) {
results = "results" <~~ json
}
}
public struct GenrePosters: Decodable {
public let poster : String
public init? (json: JSON) {
guard let poster: String = "poster_path" <~~ json
else {return nil}
self.poster = poster
}
static func updateGenrePoster(genreID: NSNumber, urlExtension: String, completionHandler:#escaping (_ details: [String]) -> Void){
var posterArray: [String] = []
let nm = NetworkManager.sharedManager
nm.getJSONData(type:"genre/\(genreID)", urlExtension: urlExtension, completion: {
data in
if let jsonDictionary = nm.parseJSONData(data)
{
guard let genrePosters = ResultsGenrePosters(json: jsonDictionary)
else {
print("Error initializing object")
return
}
guard let posterString = genrePosters.results?[0].poster
else {
print("No such item")
return
}
for posterString in genrePosters {
if posterArray.contains(posterString){continue
} else { posterArray.append(posterString) } //This is where the error happens
}
}
completionHandler(posterArray)
})
}
}
Alt + click on genrePosters and what does it tell you? It should say its ResultsGenrePosters because thats what the error is saying. Now look at the type of posterArray; its an array of String, not Array ResultsGenrePosters. I think you mean to write for poster in genrePosters and have confused yourself about the types because you wrote for posterString in genrePosters.
Maybe you want to use map to transform genrePosters into a [String] ?
This transforms your posterArray, if it exists into an array containing just the poster names. If it doesn't exist you get an empty array. This only works if poster is String. If its String? you should use flatMap instead.
let posterNames = genrePosters.results?.map { $0.poster } ?? [String]()
I apologise for the title of this question. I have no idea what else to call it.
So... When calling the following:
let testData: [NSObject : AnyObject] = getTestData()
print(testData)
I get this output:
[data: {"TypeId":7,"DataList":null,"TypeName":"This is a test"}]
How would I be able to access the value 7 for the key "TypeId"?
EDIT:
Please note that it's holding { } brackets, not only [ ], thus a cast to NSDictionary is not possible as far as I have tried.
Kind regards,
Anders
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
The way most people do it is to parse annoying JSON data as custom objects. That should be done as soon as you get the JSON. Ideally, data as JSON should not be used outside your communication code, example:
First, let's define a class to hold your server data:
class MyServerObject {
let typeId: Int
let typeName: String
let dataList: [AnyObject]?
init(dictionary: Dictionary<String, AnyObject>) {
let dataDictionary = dictionary["data"] as! Dictionary<String, AnyObject>
self.typeId = dataDictionary["TypeId"] as! Int
self.typeName = dataDictionary["TypeName"] as! String
self.dataList = dataDictionary["DataList"] as? [AnyObject]
}
}
Note that init method is already parsing the JSON. This doesn't have to be done in init, you could also create a static parse method that will return a new instance.
Usage:
// demo data
let jsonString = "{\"data\": {\"TypeId\":7,\"DataList\":null,\"TypeName\":\"This is a test\"}}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// parsing
let myServerObject = MyServerObject(dictionary: json as! Dictionary<String, AnyObject>)
// now we can simply read data as properties
print(myServerObject.typeId)
print(myServerObject.typeName)
One of the good thing about this solution is that we can check the JSON format and all the properties are parsed with the correct types.
Parsing can be hierarchical, for example, if your dataList contains complex objects, let's call them DataListItem, your parsing method can parse each item separately and put them into a [DataListItem], e.g.
if let dataListJSON = dataDictionary["DataList"] as? [Dictionary<String, AnyObject>] {
self.dataList = dataListJSON.map({ DataListItem($0) })
}
Also note that when parsing as! will crash the app when the format is invalid. as? will return nil if the types don't match. as? is very useful for types that can be nil because they are parsed as NSNull instances.
taking in account your data ...
print(testData)
/*
[data: {
DataList = null;
TypeId = 7;
TypeName = "This is a test";
}]
*/
// DataList type should be declared somewhere
class DataList {}
// parse data or set default value, if 'key' doesn't exist
if let data = testData["data"] as? [String:AnyObject] {
let dataList = data["DataList"] as? DataList // nil
let typeId = data["TypeId"] as? Int ?? 0 // 7
let typeName = data["TypeName"] as? String ?? "" // This is test
}
I have the following class which populates all the "Breakfast" entries from a JSON file. Note that in my JSON file, "ingredients" is an array and "instructions" could be an array of arrays.
In the "Populate" function below, Swift is reporting 2 errors saying, "Type 'String' has no member "PopulateArray". "Type 'AnyObject' has no member "PopulateArray".
How do I fix this?
Here's the Swift Source.
import Foundation
class Breakfast
{
var id:String = ""
var name:String = ""
var image:String = ""
var servings:String = ""
var ingredients:[String] = []
var instructions:[AnyObject] = []
func Populate(dictionary:NSDictionary) {
id = dictionary["id"] as! String
name = dictionary["name"] as! String
image = dictionary["image"] as! String
servings = dictionary["servings"] as! String
ingredients = String.PopulateArray(dictionary["ingredients"] as! [NSArray])
instructions = AnyObject.PopulateArray(dictionary["instructions"] as! [NSArray])
}
class func PopulateArray(array:NSArray) -> [Breakfast]
{
var result:[Breakfast] = []
for item in array
{
let newItem = Breakfast()
newItem.Populate(item as! NSDictionary)
result.append(newItem)
}
return result
}
}
The JSON Source can be found here: JSON Source
Neither String nor AnyObject seem to declare a method PopulateArray (unless it's in class extension you haven't included)
If your JSON has contains an array of strings for the "ingredients" key, then it will be an NSArray of NSString, which can be trivially converted to [String]:
ingredients = dictionary["ingredients"] as! [String]
Likewise, if your JSON always contains an array of AnyObject ([AnyObject]) you can populate it with:
instructions = dictionary["instructions"] as! [AnyObject]
Typically when working with JSON, you'll want to use NSJSONSerialization to convert top level JSON data into their corresponding Swift types, here's a quick example of getting data, running it through a serializer and then accessing the JSON data by casting it into known Swift data types:
if let someJsonData = someOptionalData {
if let jsonTopLevelDictionary = try? NSJSONSerialization.JSONObjectWithData(someJsonData, readingOptions: NSJSONReadingOptions.MutableContainers) as? [String:AnyObject] {
if let stringsArr = jsonTopLevelDictionary!["keyWithArrayOfStrings"] as? [String] {
for string in stringsArr {
// do stuff
}
}
}
}
There are a few different ways to skin this cat, you can even check out something like SwiftyJSON which helps parse JSON data into their native swift types.