How to check JSON can be converted to dictionary in Swift? - swift

Tried this, but get error:
var d = (try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as [String: String]
guard let d2 = d else {
return
}

You can try
guard let dic = try? JSONSerialization.jsonObject(with: data) as? [String: String] else { return }

The right tool here is JSONDecoder, not JSONSerialization:
let d = try JSONDecoder().decode([String: String].self, data)
.mutableContainers doesn't make sense when converting to a Swift Dictionary. That causes it to create NSMutableDictionary objects, which will just be converted to [String: String] exactly the same as without .mutableContainers (but possibly with an extra copy step).

Related

Adding a new value to an Encodable object

I can't find a clue to add a new value to an Encodable object and then return the result as Encodable object again, as follows:
func add(pageNumber: Int, toParams params: Encodable? = nil) -> Encodable {
var parameters: [String: Any] = [:]
if let params = params {
let jsonEncoder = JSONEncoder()
if let jsonData = try? jsonEncoder.encode(params),
let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
parameters = jsonObject
}
}
parameters["page"] = pageNumber
return parameters
}
I need the input params to be Encodable (as in the code snippet above) and not [String:Any] as it may hold objects of different types, one of them and not limited to, is a [String:Any] dictionary.
And in case there're no input params to the function, it should return an Encodable [String: Any] dictionary that holds pageNumber.

Converting to Swift 4, ambiguous subscript error

I am new to swift and currently facing issue at highlighted section of code, the error is Ambiguous use of subscript. I tried other solutions I could find on stackoverflow but could not resolve. Please help me understand the error and its solution.
do{
guard let jsonData = data else {
throw MyError.FoundNil("JSON data issue!")
}
guard let dictionaryData = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String:AnyObject] else {
throw MyError.SerializationError("Unable to serialize")
}
guard let city = dictionaryData["city"]!["name"]!,
// Error on next three lines
let data1 = dictionaryData["list"]![0]! as? [String: AnyObject],
let data2 = dictionaryData["list"]![1]! as? [String: AnyObject],
let data3 = dictionaryData["list"]![2]! as? [String: AnyObject],
let t1 = data1["t"] as? [String: AnyObject],
let t2 = data2["t"] as? [String: AnyObject],
let t3 = data3["t"] as? [String: AnyObject],
let wDay1 = data1["w"]![0]! as? [String:AnyObject],
let wDay2 = data2["w"]![0]! as? [String:AnyObject],
let wDay3 = data3["w"]![0]! as? [String:AnyObject]
else {
throw MyError.DataPopulateError("Mismatch in assigning values from dictionary")
}
First, do not use AnyObject. Define your dictionary as [String: Any].
Next, the problem is once you do something like dictionaryData["some key"] you now have an Any. That needs to be cast to do anything further with it. The error is from trying to use array index access on an Any.
Last, you are misusing the ! operator. The whole point of a guard let is to safely unwrap and safely cast a value. You defeat the whole point by using ! which will crash your app if the data isn't what your code assumes it is.
Update the 2nd guard as:
guard let dictionaryData = try? JSONSerialization.jsonObject(with: jsonData, options: []) as! [String:Any] else {
throw MyError.SerializationError("Unable to serialize")
}
Then rewrite your big guard as follows:
guard let city = (dictionaryData["city"] as? [String:Any])?["name"] as? String,
let list = dictionaryData["list"] as? [[String:Any]], list.count >= 3,
let t1 = list[0]["t"] as? [String:Any],
let t2 = list[1]["t"] as? [String:Any],
let t3 = list[2]["t"] as? [String:Any],
let wDay1 = (list[0]["w"] as? [[String:Any]])?.first,
let wDay2 = (list[1]["w"] as? [[String:Any]])?.first,
let wDay3 = (list[2]["w"] as? [[String:Any]])?.first
else {
throw MyError.DataPopulateError("Mismatch in assigning values from dictionary")
}

Parsing value with try? and guard returning different values [duplicate]

This question already has answers here:
Optional binding with try? and as? still produces an optional type
(2 answers)
Closed 5 years ago.
Language Used: Swift 3
Xcode Version: 8.3.2 (8E2002)
I have an extension on Data which parses the data into a JSONObject of type Any
extension Data {
func toJsonObject() -> Any? {
do {
return try JSONSerialization.jsonObject(with: self, options: [])
} catch {
print(error)
}
return nil
}
}
Now the weird thing is when I use guard the result object seems to be different when using toJsonObject() and try?
For example
guard let dictionary = data.toJsonObject() as? [String: Any] else {
return
}
dictionary is now of type [String: Any]
Whereas when I use this:
guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return
}
dictionary is now of type [String: Any]?
Isn't the result of the second code block supposed to be [String: Any] instead of the optional [String: Any]?
Is this a mistake on Swift or am I doing something wrong?
This is a feature of Swift, I'd say. The use of try? means that this becomes a double optional, it's trying to decode straight to that casting of as? [String: Any]. Plug this in and check the type on this to see the double optional in action:
// Becomes a type of `[String : Any]??`
let dictionary = try? JSONSerialization.jsonObject(with: Data(), options: []) as? [String: Any]
I think the answer you want is to just add some parentheses to clarify your intent:
guard let dictionary = (try? JSONSerialization.jsonObject(with: Data(), options: [])) as? [String: Any] else {
return
}

Getting nil value when read JSON data in Swift

if let postString = NSString(data:data!, encoding: NSUTF8StringEncoding) as? String {
guard let jsonData = postString.dataUsingEncoding(NSASCIIStringEncoding) else {
fatalError()
}
guard let jsonObjects = try? NSJSONSerialization.JSONObjectWithData(jsonData,options: [])
,let JSONArray = jsonObjects as? [[String: AnyObject]]
else {
fatalError()
}
print(JSONArray)
}
In postString constant, I am getting "[{\"Name\":\"ABC\",\"Age\":35},{\"Name\":\"CDE\",\"Age\":36‌​}]"
and when I run this code then fatalError() code call.
A couple of other people have explained what you did wrong in this case (Tried to cast the output of deserialization to a dictionary when it actually contains an array.)
Stepping back from the details, when something fails, you need to break your code into smaller pieces and then trace through it to see what's failing.
The "as?" cast says "Try to cast this object to another type. Wrap the results in an Optional." If the cast fails, the result is nil. If it succeeds, the optional contains the new type.
If you rewrote your code as:
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data!,
options: NSJSONReadingOptions.AllowFragments)
guard let jsonDict = jsonObject as [String: Any] else {
print("Cast failed")
return
}
Then the print statement would fire and you'd know that the cast was the problem.
EDIT:
I just noticed that your JSON data contains an array of dictionaries of type [String: Int]. In Swift Ints are not an Object type, so you need to cast your results to [[String:Any]], not [[String:AnyObject]]. I've fixed my code above.
I Wrote the following code in a playground and it works:
let jsonString = "[{\"Name\":\"ABC\",\"Age\":35},{\"surveyName\":\"CDE\",\"Age\":36}]"
guard let jsonData = jsonString.data(using: .ascii) else {
fatalError()
}
guard let jsonObjects = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let JSONArray = jsonObjects as? [[String: Any]]
else {
fatalError()
}
print(String(describing: jsonObjects))
It gives the output:
(
{
Age = 35;
Name = ABC;
},
{
Age = 36;
surveyName = CDE;
}
)
Which is what I would expect.
EDIT #2:
Actually, on further investigation I'm stumped as to why your code isn't working. I just tested it, and the as [[String: AnyObject]] works. It turns out that in Swift 3 if you cast an Int to AnyObject and you've included Foundation (or UIKit) then it gets silently converted to an NSNumber, which IS an Object type.
You're going to need to show your actual JSON data and the code that converts it to an object if you need help debugging it.
EDIT #3:
Below is code I wrote and tested in Swift 2.3:
func parseJSONTest() {
let jsonString = "[{\"Name\":\"ABC\",\"Age\":35},{\"surveyName\":\"CDE\",\"Age\":36}]"
guard let jsonData = jsonString.dataUsingEncoding(NSASCIIStringEncoding) else {
fatalError()
}
//I'm not sure why you take JSON data, convert it to a string, and convert
//It back to NSData, but to prove a point, this is your code being fed
//well-formed JSON data in an NSData object:
if let postString = NSString(data:jsonData,
encoding: NSASCIIStringEncoding) as? String {
guard let jsonData = postString.dataUsingEncoding(NSASCIIStringEncoding) else {
fatalError()
}
guard let jsonObjects = try? NSJSONSerialization.JSONObjectWithData(jsonData,options: []),
let JSONArray = jsonObjects as? [[String: AnyObject]]
else {
fatalError()
}
print(JSONArray)
}
}
In order to provide a complete test I first take a string containing JSON data and convert it to NSData. I then convert that NSData back to JSON objects and cast them to the desired type, and it works. The code above displays:
[["Name": ABC, "Age": 35], ["surveyName": CDE, "Age": 36]]
Which matches the structure you have (an array of dictionaries).
The above is technically not an json object it is a json array.
Try casting as [AnyObject] instead of [String: AnyObject]
Then use the array to access the item you need. Then cast that to
[String:AnyObject]
because they are json objects within a json array

Problems with getting values out of nested dictionary in Swift 3 and Xcode 8

I parse JSON with this :
let dictionary = try JSONSerialization.jsonObject(with: geocodingResultsData as Data, options: .mutableContainers)
and get the following nested dictionary as a result
{ response = { GeoObjectCollection = { featureMember =
(
{ GeoObject = { Point = { pos = "40.275713 59.943413"; }; }; },
{ GeoObject = { Point = { pos = "40.273162 59.944292"; }; }; }
);
};
};
}
I'm trying to get the values of coordinates out of this dictionary and save them into new latutudeString and longitudeString variables
Until Xcode 8 GM it worked for me with this code:
if let jsonCoordinatesString: String = dictionary["response"]!!["GeoObjectCollection"]!!["featureMember"]!![0]["GeoObject"]!!["Point"]!!["pos"]!! as? String {
var latLongArray = jsonCoordinatesString.components(separatedBy: " ")
let latitudeString = latLongArray[1]
let longitudeString = latLongArray[0]
}
But since I've installed Xcode 8 GM i receive an error:
Type Any has no Subscript members
How to fix it it Swift 3 with Xcode 8 ? I've read that I can cast it but don't know exactly how to make it work with my nested dictionary in swift 3 with the latest Xcode. I've read can't resolve "Ambiguous use of subscript" but it really did not helped me in my case.
Your JSON data has this type in Swift:
[String: [String: [String: [[String: [String: [String: String]]]]]]]
I would avoid using such a too deeply nested type, but you can write something like this, if you dare use it:
enum MyError: Error {
case invalidStructure
}
do {
guard let dictionary = try JSONSerialization.jsonObject(with: geocodingResultsData as Data, options: .mutableContainers) as? [String: [String: [String: [[String: [String: [String: String]]]]]]] else {
throw MyError.invalidStructure
}
if let jsonCoordinatesString: String = dictionary["response"]?["GeoObjectCollection"]?["featureMember"]?[0]["GeoObject"]?["Point"]?["pos"] {
var latLongArray = jsonCoordinatesString.components(separatedBy: " ")
let latitudeString = latLongArray[1]
let longitudeString = latLongArray[0]
}
} catch let error {
print(error)
}
But you may be hiding some irrelevant members of the JSON data, which might break this as? conversion.
So, you can go step by step, in some cases "need to" in Swift 3, like this:
do {
guard let dictionary = try JSONSerialization.jsonObject(with: geocodingResultsData as Data, options: .mutableContainers) as? [String: AnyObject] else {
throw MyError.invalidStructure
}
if
let response = dictionary["response"] as? [String: AnyObject],
let geoObjectCollection = response["GeoObjectCollection"] as? [String: AnyObject],
let featureMember = geoObjectCollection["featureMember"] as? [[String: AnyObject]],
!featureMember.isEmpty,
let geoObject = featureMember[0]["GeoObject"] as? [String: AnyObject],
let point = geoObject["Point"] as? [String: AnyObject],
let jsonCoordinatesString = point["pos"] as? String
{
var latLongArray = jsonCoordinatesString.components(separatedBy: " ")
let latitudeString = latLongArray[1]
let longitudeString = latLongArray[0]
}
} catch let error {
print(error)
}
(lets are mandatory for each Optional-bindings in Swift 3. And you can change all AnyObjects to Anys, if you prefer.)
The problem is that you have not specify the type of dictionary object, you need to explicitly specify the type of dictionary object as Dictionary like this way.
let dictionary = try JSONSerialization.jsonObject(with: geocodingResultsData as Data, options: .mutableContainers) as! [String: Any]
if let response = dictionary["response"] as? [String: Any],
let geoObjectCollection = response["GeoObjectCollection"] as? [String: Any],
let featureMember = geoObjectCollection["featureMember"] as? [[String: Any]] {
if let geoObject = featureMember[0]["GeoObject"] as? [String: Any],
let point = geoObject["Point"] as? [String: String] {
let latLongArray = point["pos"].components(separatedBy: " ")
let latitudeString = latLongArray[1]
let longitudeString = latLongArray[0]
}
}