JSONSerialization AnyObject SWIFT3 Conversion Issues - ios

I have converted to Swift 3 and I have received the following errors when assigning to AnyObject the JSONSerialization.jsonObject. Has anyone come across this issue and know the fix?

Since the last Swift 3 update most of the return types changed from AnyObject to Any and downcast is not allowed, so in such situation you are forced to use explicit cast. That means you should make a couple of guard statements or use optional chaining if let defining each necessary field. Consider using map, filter, reduce if possible to make your code more elegant. Example:
guard way:
guard let object = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return nil }
guard let field1 = object[0]["field1_token"] as? [Any] else { return nil }
//do your thing
if let way:
if let object = try JSONSerialization.jsonObject(with: data) as? [[String: Any]],
let field1 = object[0]["field1_token"] as? [Any] {
//do your thing
}
You may want to check Apple's article Working with JSON in Swift
Also you can use some of the json parsing/mapping libriaries like these:
SwiftyJSON
Gloss

Please replace let object : AnyObject with let object : Any.
Error showing because of wrong casting.

Related

unable to access JSON array in swift

I have a JSON file and I'm trying to access the array in it.
The JSON file looks like:
{
"cars": [{
"name": "BMW",
"icons": [["front.png", "back.png", "B3"],
["front_red", "back_red", "C4"]
]
}]
}
//cars is an array of dictionaries, I just mentioned one in the snippet.
I get the JSON data as:
func loadJSONData(){
if let path = Bundle.main.path(forResource: "testJSON", ofType: "json")
{
if let jsonData = NSData(contentsOfFile : path)
{
do {
if let jsonResult = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:Any]
{
self.testJSONData = (jsonResult["cars"] as? Array)!
//self.testJSONData = (jsonResult["cars"] as? Array<Dictionary<String, Any>>)! //also tried this
}
}
catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
testJSONData is declared as an array:
var testJSONData = [] as [Dictionary<String, Any>]
and the error occurs when trying to get the "icons" array from the JSON.
let namePredicate = NSPredicate(format: "name like BMW")
let filteredArray :Array = testJSONData.filter() { namePredicate.evaluate(with: $0) }
let carData: Dictionary = filteredArray[0] as Dictionary<String, Any>
let carIcons: Array = carData["icons"] as! Array //error at this line
Cannot convert value of type 'Array<_>' to specified type 'Array'
Can someone please show me where I'm doing wrong ? Thanks!
Array is a generic type in Swift, so when you want to declare an array variable, you always need to specific what type of elements the Array is holding. There's no such type as Array without specifying its Element type.
Also, there's no need for type annotations in Swift, the compiler can infer the types for you and you are explicitly telling the compiler the type by casting anyways.
carIcons should be of type Array<Array<String>> or as a shorthand [[String]]
let carIcons = carData["icons"] as! [[String]]
Some general comments about your code: don't use old Foundation types, such as NSData in Swift when they have native Swift equivalents. Also don't do force unwrapping of safe casted types, that makes no sense. Either handle the casting and unwrapping safely or simply force cast if you know the cast will succeed for sure. .mutableContainers have no effect in Swift, so don't use it. There's no need to cast error to NSError in a catch block, Swift has its own Error type.
func loadJSONData(){
if let fileURL = Bundle.main.url(forResource: "testJSON", withExtension: "json") {
do {
let jsonData = try Data(contentsOfFile: fileURL)
if let jsonResult = try JSONSerialization.jsonObject(with: jsonData) as? [String:Any], let cars = jsonResult["cars"] as? [[String:Any]] {
self.testJSONData = cars
} else {
print("Unexpected JSON format")
}
}
catch {
print(error)
}
}
}
However, if you are using Swift 4, the best solution would be using the type safe Codable protocol and JSONDecoder instead of JSONSerialization.

How can I read Plist in Swift?

I want read a NSArray form plist , and the code :
func loadPlistArray() -> [Any] {
var path: String? = Bundle.main.path(forResource:"MyCenter", ofType: "plist")
if let arry = NSArray(contentsOfFile: path!) {
return arry as! NSArray
}else{
return nil;
}
}
but always got errors below:
And After I got the data from plist, I fount that I can't see the Details of Dictionary :
And here is my plist:
should I add a generic in the array by var plistArray : [[String:Any]]?
The errors messages you are getting tell you what is wrong with your method, this is how I would write the function:
func loadPlistArray() -> [Any] { // 1
guard
let url = Bundle.main.url(forResource: "MyCenter", withExtension: "plist"), // 2
let list = NSArray(contentsOf: url) as? [Any] // 3
else { return [] } // 4
return list
}
And some commentary:
You are declaring the method to return an Array of Any items, but your method tries to return an NSArray.
It is recommended to use the URL based methods for accessing files, rather then the string based paths.
You have to use the Array methods to read the plist, but you can cast it to [Any]. However, if you know the type of items you have in the plist, I recommend that you return a properly type array from this method e.g. [String], [Int] etc.
You don't need to return an optional if the file can't be read. Depending on how you want to handle the error you could either return an empty array (as I've shown here) or convert your function into a throwing one so that if you can't read the file an error is thrown and can be handled by the calling code.
Your method signature clearly states that it returns an [Any] (i.e., Swift native Array containing elements of any type whatsoever), while you try to cast the return value into NSArray (even though it already is by virtue of intialization: NSArray(contentsOfFile:)).
Change it to:
return arry as? [Any]
// (will return nil if the cast fails - not a problem if you
// also apply the fix mentioned below...)
The other path tries to return nil; for that to be acceptable, your signature needs to be defined as returning an optional:
func loadPlistArray() -> [Any] // WRONG
func loadPlistArray() -> [Any]? // RIGHT
EDIT: If your app is structured in such a way that you can't afford to return nil from your method, you can instead return an empty array on failure:
else {
return [] // Empty array
}
(use [:] for empty dictionary)
Also, try to avoid using ! whenever possible, and switch to ? instead, unless you are 100% sure that whatever it is you are forcing will not fail and cause a runtime error (crash).
return (arry ) as! [Any]
You cannot return NSArray on type Any
I am using something like this in my project.
if let fileUrl = Bundle.main.url(forResource: "new", withExtension: "plist"),
let myDict = NSDictionary(contentsOf: fileUrl) as? [String:Any] {
print(myDict)
}
I have another plist for color which have array as a root.
if let fileUrl = Bundle.main.url(forResource: "color", withExtension: "plist"),
let data = try? Data(contentsOf: fileUrl) {
if let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [Any] {
print(result)
}
}

Ambiguous use of 'subscript' issue at json array [duplicate]

I got this Errors after I converting to swift 2.3.
guard let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
guard
let loadedWeather = json["weather"]![0]["description"] as? String,
let loadedTemperatur = json["main"]!["temp"] as? Float,
let loadedWindSpeed = json["wind"]!["speed"] as? Float
else {
print("Weather JSON-Parsing failed")
return
}
The Ambiguous use of subscript error comes by declaring "loadedWeather, loadedTemperatur and loadedWindSpeed".
Already tried to change NSDictionary to Dictionary and other things, helped on another position in code, but here....
thanks guys
This happens because compiler doesn't know what are the intermediary object is in each of your line ... so may be
if let weather = json["weather"] as? [[String:String]], firstObject = weather.first as? [String:String]{
let loadedWeather = firstObject["description"]
}
// same for other objects i.e. `json["main"]` and `json["wind"]` with its return type
I think that the issue is that the compiler cannot work out what json["weather"] is, You may need to be more specific in your code.
Try
let loadedWeather = (json["weather"] as! [[String:AnyObject]])[0]["description"] as? String

swift for loop not looping enough times

I have a tableView in my app, and when i load the app I want the view to be populated with a list of dogs (retrieved from a server).
I have this working, but it will only load the first dog in the list from the server.
here's the code starting from where it serialises the JSON response from the server
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as! [AnyObject]
dispatch_async(dispatch_get_main_queue(), {
self.tableView.beginUpdates()
if let theDogs = json[0] as? [[String: AnyObject]] {
for dog in theDogs {
print("Dog")
if let ID = dog["ID"] as? String {
print(ID + " Safe")
let thisDog = Dog(name: (dog["Name"] as? String)!, surname: (dog["Surname"] as? String)!, id: (dog["ID"] as? String)!, boarding: true)
let newIndexPath = NSIndexPath(forRow: self.dogs.count, inSection: 0)
// code here
self.dogs.append(thisDog)
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
self.tableView.endUpdates()
})
} catch {
print("error serializing JSON: \(error)")
}
here's a copy of the logs (which includes a printed response from the server)
Optional([[{"ID":"47","Name":"Sparky","Surname":"McAllister"}],
[{"ID":"31","Name":"Maddie","Surname":"Crawford"}]])
Dog
47 Safe
as you can see from the log, there are 2 dogs on this list.
I would expect to see Dog printed twice in the log if the loop was working and 31 Safe if it was getting to the part of the code that creates a new Dog object.
I can't work out what i've done wrong, can anyone see my problem?
thanks
Because the JSON is an Array of Arrays containing one dictionary each when you call if let theDogs = json[0] you get this part of the JSON: [{"ID":"47","Name":"Sparky","Surname":"McAllister"}]
You would need to call if let theDogs = json[1] to get this part of the JSON:
[{"ID":"31","Name":"Maddie","Surname":"Crawford"}]
Ok, thanks to Travis' answer I was able to see where i'm going wrong. I just made a little tweak to his suggestion so i'm posting as an answer.
as Travis said, I need too access json[1] but i could have 7 different dogs on that list!
so i made the following changes:
if let theDogs = json[0] as? [[String: AnyObject]] {
is now:
if let theDogs = json as? [[AnyObject]] {
which means in the for loop i'm accessing the root array.
I then changed the for loop from:
for dog in theDogs{
to:
for aDog in theDogs{
let dog = aDog[0]
which means for every array in theDogs, i'll get the only object in the array and call it dog.
problem solved, and future proofed.
thanks to everyone that helped!

Conditional Binding Error in Swift

I am working on an example project (Written in an earlier version of Swift) in my efforts to learn Swift 2, and have run into a problem
I am getting a compile error with this -
class func loadMembersFromFile(path:String) -> [Member]
{
var members:[Member] = []
var error:NSError? = nil
if let data = NSData(contentsOfFile: path, options:[]),
json = NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary,
team = json["team"] as? [NSDictionary] {
for memberDictionary in team {
let member = Member(dictionary: memberDictionary)
members.append(member)
}
}
return members
}
The errors are:
Initializer for conditional binding must have Optional type, not 'NSData'
and
Call can throw, but it is not marked with 'try' and the error is not handled
My Swift programming experience is quite limited, so I have not been able to find a way to correct these errors. Any suggestions would be appreciated. Thanks.
Initializer for conditional binding must have Optional type, not
'NSData'
Means that you don't need conditional binding when result is not optional. But this is not true issue here. Because NSData initialiser can throw an error (which is stated in second error) and you may convert it to optional. Here is how your code will look:
class func loadMembersFromFile(path:String) -> [Member]
{
var members:[Member] = []
var error:NSError? = nil
if let data = try? NSData(contentsOfFile: path, options:[]),
json = (try? NSJSONSerialization.JSONObjectWithData(data, options: [])) as? NSDictionary,
team = json["team"] as? [NSDictionary] {
for memberDictionary in team {
let member = Member(dictionary: memberDictionary)
members.append(member)
}
}
return members
}

Resources