Okay, so, swift noob alert:
How do I do the simplest of iterations given the following array (I dont know what to call this shape: array, dictionary, object...)?
func showNotification(_ sender: [AnyHashable: Any]) { ... }
sender["actions"]:
Optional([{"text":"Confirm","type":"response"},{"text":"Decline","type":"response"},{"link":"https://www.stackoverflow.com","text":"Website","type":"info"}])
attempts:
if let result = sender["actions"] {
print("YES \(result)")
for action in result as! [String] {
print(action)
}
}
the above prints:
YES [{"text":"Confirm","type":"response"},{"text":"Decline","type":"response"},{"link":"https:\/\/www.stackoverflow.com","text":"Website","type":"info"}]
...however, returns the following error:
Could not cast value of type '__NSCFString' (0x1a7c28d50) to 'NSArray' (0x1a7c297c8)
The end goal here is to simply get to each one of the actions individually, ie:
{"text":"Confirm","type":"response"}
{"text":"Decline","type":"response"
ect...
Does Swift have a map function... FYI Im coming in from a Java and JavaScript world... swiftyjson seems a bit heavy for one loop.
Thanks, and as always any help and direction is appreciated!
edit:
This is the print via the param passed to the function sender:
sender: [AnyHashable("title"): title!, AnyHashable("message"): message, AnyHashable("message_id"): 0:1503511875428318%03300c3203300c32, AnyHashable("id"): 1497708240713, AnyHashable("actions"): [{"text":"Confirm","type":"response"},{"text":"Decline","type":"response"},{"link":"https:\/\/www.notifyd.com","text":"Website","type":"info"}], AnyHashable("aps"): {
"content-available" = 1;
}]
You want to decode the JSON String and then cast to an array of Dictionary:
if
// cast sender["actions"] to String
let actionsString = sender["actions"] as? String,
// decode data of string and then cast to Array<Dictionary<String, String>>
let actionsStringData = actionsString.data(using: .utf8),
let result = try JSONSerialization.jsonObject(with: actionsStringData, options: []) as? [[String : String]]
{
print("YES \(result)")
for action in result {
print(action)
}
}
Is it really true that what you've got here is an undecoded JSON string? In that case, Swift 4 makes this really easy:
struct S : Decodable {
let link : String?
let text : String
let type : String
}
if let acts = sender["actions"] as? String {
let data = acts.data(using: .utf8)!
if let arr = try? JSONDecoder().decode(Array<S>.self, from: data) {
arr.forEach {print($0)}
}
}
/*
S(link: nil, text: "Confirm", type: "response")
S(link: nil, text: "Decline", type: "response")
S(link: Optional("https://www.stackoverflow.com"), text: "Website", type: "info")
*/
There is something fishy going on here with the data. Let's treat the data with more care. Here is a method to get JSON from a JSON object, a JSON array, a Data object or a string.
enum JsonError: Error { case notJson; case notJsonArray }
func json(from any: Any?) throws -> Any {
if let json = any as? [String: Any] { return json }
if let json = any as? [Any] { return json }
if let data = any as? Data {
return try JSONSerialization.jsonObject(with: data)
}
if let string = any as? String, let data = string.data(using: .utf8) {
return try JSONSerialization.jsonObject(with: data)
}
throw JsonError.notJson
}
Now that I'm being more careful with the JSON object, I should get the what I want or know more about the error.
func showNotification(_ sender: [AnyHashable: Any]) {
do {
guard let result = try json(from: sender["actions"]) as? [Any] else {
throw JsonError.notJsonArray
}
print("YES \(result)")
for action in result {
print("Action: \(action)")
}
} catch {
// Do Something
}
}
Related
I use alamofire for requests. Here on a simple response, I'm getting this JSON string from alamofire
Optional("[{\"nl2br\":\"2019-05-02 19:52:10\",\"nl3br\":\"\",\"nl4br\":\"#lxr\",\"nl5br\":\"1\",\"nl6br\":\"nein\",\"nl7br\":\"bARxrdw9c7WS1RN9c\\/\\/MrA==822b5dd20cb73611:35cab43d0c7e82e73c62818d9f90cfe8\",\"nl8br\":\"tBDSzkQ7eCNERwldgZiwKg==a7590d9d8affdbe5:63b9a9bf6421ea7db2a55f8773990b08\",\"nl9br\":\"fpd17pxVUzcJWNskVWyBeA==cf21783de8334248:a93e8b8a92ef9b238b75ad87a315ce3e\"},{\"nl2br\":\"2019-04-26 21:36:32\",\"nl3br\":\"old\",\"nl4br\":\"\",\"nl5br\":null,\"nl6br\":\"ja\",\"nl7br\":\"emjUIzK92fWwHNLv\\/4xv2Q==8fca202c9a816c7b:655ef8aaaa0212bb9e77a9c35a56c3a7\",\"nl8br\":\"1C5Hy\\/ZhMk3b6SZY8c08lw==cb3b0f4fdd6b5957:034e72d400598fedc43b3111d841a31b\",\"nl9br\":\"KUfLHbih9612dhNCYPOrTg==48da9c3362430e01:3a80ba9fb516e4172aa39b017abcd96e\"},{\"nl2br\":\"2019-04-26 21:20:24\",\"nl3br\":\"holder\",\"nl4br\":\"\",\"nl5br\":null,\"nl6br\":\"ja\",\"nl7br\":\"J59nMBWUMyU9PzdbwQqd0g==f7d3c6c561c33f0b:3465e9131a2577c39cddcc606d0785b0\",\"nl8br\":\"G4rRevk951ZBJJQKzBeJZg==45c0ebcdc1e000e8:dc770c3849fef8b058f85cad42c73e1d\",\"nl9br\":\"338Ud9CIDgnyBV96F2Fx\\/w==d0f825064e0ecf81:0ca00ff27e977ef5f8123d38c643a8b3\"},{\"nl2br\":\"2019-04-10 23:57:17\",\"nl3br\":\"\",\"nl4br\":\"Wasmes, here\'s a video and do ya need?\",\"nl5br\":\"1\",\"nl6br\":\"ja\",\"nl7br\":\"fw17O1L9SuO9FS3qu6U7QQ==706bd09ee3b5ec55:6667d9020a0edcacbd217f631d2305c0\",\"nl8br\":\"2tTE9+0Kr6cVSuEOsq8h0w==3e0e46352da323be:941710bd881546dfa08d3afb6aea2831\",\"nl9br\":\"NAkMx0OsKwXVGndYXbAFmA==1234c0b2992502bf:666f8efd3bdf8ee8b0a2ee4e7222c2ef\"},{\"nl2br\":\"2019-02-22 21:38:16\",\"nl3br\":\"i\'m giving up on you, but don\'t \",\"nl4br\":\"\",\"nl5br\":null,\"nl6br\":\"nein\",\"nl7br\":\"gOv13Dsxf1N2UG46KT3tvQ==dd02a83b1837bb24:4f7c7ab95e873d35685ab7b738626f44\",\"nl8br\":\"40+GOpcNU6M4F688DJyxFA==70bc177842f3f6cf:232b065423bd2683d75ca177775fd352\",\"nl9br\":\"mBc3pEHKBiiPeAEpZRskug==71b353fed6403b99:c58099676beacc146c14867645e29783\"}]")
I get my data like this and convert it like this
if let responseData = response.data {
let responseDataString = String(data: responseData, encoding:String.Encoding(rawValue: String.Encoding.utf8.rawValue))
print(responseDataString) // gives the whole string above
if let obj = responseDataString?.toJSON() as? [String:AnyObject] {
print(obj["nl2br"] as! String) // gives nothing, just nil
}
}
My string extension is like this.
extension String {
func toJSON() -> Any? {
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
}
}
Any idea why this behaviour is expected? I tried printing obj that gives nothing too.
Your root is an array not a dictionary Change responseDataString?.toJSON() as? [[String:Any]]
extension String {
func toJSON() -> Any? {
return try? JSONSerialization.jsonObject(with: Data(self.utf8))
}
}
let responseDataString:String? = """[{\"nl2br\":\"2019-05-02 19:52:10\",\"nl3br\":\"\",\"nl4br\":\"#lxr\",\"nl5br\":\"1\",\"nl6br\":\"nein\",\"nl7br\":\"bARxrdw9c7WS1RN9c\\/\\/MrA==822b5dd20cb73611:35cab43d0c7e82e73c62818d9f90cfe8\",\"nl8br\":\"tBDSzkQ7eCNERwldgZiwKg==a7590d9d8affdbe5:63b9a9bf6421ea7db2a55f8773990b08\",\"nl9br\":\"fpd17pxVUzcJWNskVWyBeA==cf21783de8334248:a93e8b8a92ef9b238b75ad87a315ce3e\"},{\"nl2br\":\"2019-04-26 21:36:32\",\"nl3br\":\"old\",\"nl4br\":\"\",\"nl5br\":null,\"nl6br\":\"ja\",\"nl7br\":\"emjUIzK92fWwHNLv\\/4xv2Q==8fca202c9a816c7b:655ef8aaaa0212bb9e77a9c35a56c3a7\",\"nl8br\":\"1C5Hy\\/ZhMk3b6SZY8c08lw==cb3b0f4fdd6b5957:034e72d400598fedc43b3111d841a31b\",\"nl9br\":\"KUfLHbih9612dhNCYPOrTg==48da9c3362430e01:3a80ba9fb516e4172aa39b017abcd96e\"},{\"nl2br\":\"2019-04-26 21:20:24\",\"nl3br\":\"holder\",\"nl4br\":\"\",\"nl5br\":null,\"nl6br\":\"ja\",\"nl7br\":\"J59nMBWUMyU9PzdbwQqd0g==f7d3c6c561c33f0b:3465e9131a2577c39cddcc606d0785b0\",\"nl8br\":\"G4rRevk951ZBJJQKzBeJZg==45c0ebcdc1e000e8:dc770c3849fef8b058f85cad42c73e1d\",\"nl9br\":\"338Ud9CIDgnyBV96F2Fx\\/w==d0f825064e0ecf81:0ca00ff27e977ef5f8123d38c643a8b3\"},{\"nl2br\":\"2019-04-10 23:57:17\",\"nl3br\":\"\",\"nl4br\":\"Wasmes, here\'s a video and do ya need?\",\"nl5br\":\"1\",\"nl6br\":\"ja\",\"nl7br\":\"fw17O1L9SuO9FS3qu6U7QQ==706bd09ee3b5ec55:6667d9020a0edcacbd217f631d2305c0\",\"nl8br\":\"2tTE9+0Kr6cVSuEOsq8h0w==3e0e46352da323be:941710bd881546dfa08d3afb6aea2831\",\"nl9br\":\"NAkMx0OsKwXVGndYXbAFmA==1234c0b2992502bf:666f8efd3bdf8ee8b0a2ee4e7222c2ef\"},{\"nl2br\":\"2019-02-22 21:38:16\",\"nl3br\":\"i\'m giving up on you, but don\'t \",\"nl4br\":\"\",\"nl5br\":null,\"nl6br\":\"nein\",\"nl7br\":\"gOv13Dsxf1N2UG46KT3tvQ==dd02a83b1837bb24:4f7c7ab95e873d35685ab7b738626f44\",\"nl8br\":\"40+GOpcNU6M4F688DJyxFA==70bc177842f3f6cf:232b065423bd2683d75ca177775fd352\",\"nl9br\":\"mBc3pEHKBiiPeAEpZRskug==71b353fed6403b99:c58099676beacc146c14867645e29783\"}]"""
if let obj = responseDataString?.toJSON() as? [[String:Any]] {
obj.forEach {
print($0["nl2br"])
}
}
Result
Optional(2019-05-02 19:52:10)
Optional(2019-04-26 21:36:32)
Optional(2019-04-26 21:20:24)
Optional(2019-04-10 23:57:17)
Optional(2019-02-22 21:38:16)
Edit: In json this
[{
"name":"ppp"
}]
is an array of dictionaries to be [[String:Any]] notice nested [[]] , while this
{
"name":"ppp"
}
is a dictionary represented by [String:Any] notice only []
Needs to get country name from below api call :
https://restcountries.eu/rest/v1/all
My code :
var arrRes = []
func getCountry() {
let Url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(Url).responseJSON { (responseData) -> Void in
do {
if let datas = responseData.result.value {
let data = (datas as AnyObject).data(using: .utf8)!
let parseData = try JSONSerialization.jsonObject(with: data, options: [])
for country in parseData {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
}
}
getting error here : 'Any' is not convertible to 'AnyObject' on below line let data = (datas as AnyObject).data(using: .utf8)!..
I need to get only name and append to my array.Any other idea or solution to achieve that ?
Replace do catch block of statement with this.
do {
if let countries = responseData.result.value as? [[String: Any]] {
for country in countries {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
Try this, its working fine for me.
let urlStr = "https://restcountries.eu/rest/v1/all"
let setFinalURl = urlStr.addingPercentEncoding (withAllowedCharacters: .urlQueryAllowed)!
var request = URLRequest(url: URL(string: setFinalURl)!)
request.httpMethod = HTTPMethod.get.rawValue
Alamofire.request(request).responseJSON
{ (responseObject) -> Void in
if responseObject.result.isSuccess
{
print(responseObject.result.value!)
if "\(String(describing: responseObject.response!.statusCode))" == "200"
{
let result = responseObject.result.value! as AnyObject
let countryNamesArr = result.value(forKey: "name") as! NSArray
print(countryNamesArr)
}
else
{
// handle error
}
}
if responseObject.result.isFailure
{
let error : Error = responseObject.result.error!
print(error.localizedDescription)
}
}
You can try
struct Root: Codable {
let name: String
}
func getCountry() {
let urlStr = "https://restcountries.eu/rest/v1/all"
Alamofire.request(urlStr).responseData { (data) in
do {
guard let data = data.data else { return }
let res = try JSONDecoder().decode([Root].self,from:data)
print(res)
}
catch {
print(error)
}
}
}
Just remove this line
let data = (datas as AnyObject).data(using: .utf8)!
and in optional binding just assign data, since value is of type Data?, from optional binding you get Data
if let data = responseData.result.value
then don't forget to downcast your json to array [String:Any]
...jsonObject(with: data, options: []) as? [[String:Any]]
... then don't forget to unwrap this array or you wouldn't be able to iterate through it in for each loop
Also note that since there is Codable, you should use it instead of JSONSerialization. Then you can decode your json using JSONDecoder to your own model which conforms to protocol Decodable.
As a simple approach, you could implement getCountry() like this:
func getCountry() {
let url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(url).responseJSON { response in
if let resultValue = response.result.value, let countryObjects = resultValue as? [[String: Any]] {
let countryNames = countryObjects.compactMap { $0["name"] as? String }
print(countryNames)
}
}
}
At this point, there is no need to use JSONSerialization to get the country names; According to the API response, responseData.result.value is an array of countries (dictionaries), each dictionary has a "name" value, what you should do is to map the response to an array of string. countryNames should contains what are you looking for.
The benefit of using compactMap is to avoid any nil name, so countryNames should be [String] instead of [String?].
However, if you believe that you would need to transform the whole response objects into a custom objects (instead of dictionaries), I would highly recommend to follow the approach of using Decodable.
My code, its working well for me.
Swift 5
public func getCountry(completion: #escaping ([String]) -> ()) {
let url: String = "https://restcountries.eu/rest/v1/all"
AF.request(url).responseJSON { (responseData) -> Void in
do {
guard let data = responseData.data else { return }
let res = try JSONDecoder().decode([CountryName].self,from:data)
completion(self.getCountryName(countryName: res))
}
catch {
print(error)
}
}
}
struct CountryName: Codable {
let name: String
}
private func getCountryName(countryName:[CountryName]) -> [String]{
var country:[String] = []
for index in 0...countryName.count - 1{
country.append(countryName[index].name)
}
return country
}
I am trying to parse some nested JSON retrieved through an API but am having trouble isolating specific key-value pairs. In fact, I have some confusion over the difference between the JSON data and the dictionary obtained through serialization.
To retrieve the data I am using:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
return
}
To convert the data to a JSON dictionary, I am doing
do {
let stringDic = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
} catch let error {
print(error)
}
When printed, this produces nested output of the form:
Optional(["document_tone": {
"tone_categories" = (
{
"category_id" = "emotion_tone";
"category_name" = "Emotion Tone";
and so forth
My question is how can I get a unique value such as that for the key category_name?
If I try to use
let myCat = stringDic["category_name"]
Fix-it requires let document_tone = stringDic?["document_tone"] which if printed to console just prints whole dictionary over again.
Thanks in advance for any suggestions.
It's pretty easy: () is array, {} is dictionary and the compiler must know the static types of all subscripted objects:
if let documentTone = stringDic?["document_tone"] as? [String:Any],
let toneCategories = documentTone["tone_categories"] as? [[String:Any]] {
for category in toneCategories {
print(category["category_name"])
}
}
I think it's better to use Decodable
struct Root:Decodable {
let documentTone : InnerItem
}
struct InnerItem:Decodable {
let toneCategories: [BottomItem]
}
struct BottomItem:Decodable {
let categoryId: String
let categoryName: String
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data)
//print all names
result.documentTone.toneCategories.forEach {print($0.categoryName) }
} catch {
print(error)
}
I am using Alamofire to call the Riot API and I want to display the information that it has called. I have the get request working, I just don't know how to link to a label in the application. I have included screenshots of the code!
Code
Response
It is just a simple app I am creating!
func callAlamo(url: String){
Alamofire.request(url).responseJSON(completionHandler: {
response in
self.pasrseData(JSONData: response.data!)
})
}
func parseData(JSONData: Data){
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as? JSONStandard
print(readableJSON)
}
catch {
print(error)
}
}
No need to serialize since responseJSONfrom Alamofire has done it. Since I don't know what is inside of your JSON object, let's say that you get a return of age and name:
struct InfoModel { // this struct will decompose our JSON object from the response that we get
var age:Int
var name:String
init(json:Dictionary<String,Any>?) {
guard let dict = json,
let age = dict["age"] as? Int,
let name = dict["name"] as? String
else {fatalError() }
self.age = age
self.name = name
}
}
func parse(url: String, completion:#escaping (InfoModel)-> Void) {
Alamofire.request(url).responseJSON {response in
// get the JSON dictionary
if let JSON = response.result.value {
// no need to decompose your object since your struct does it inside of its initializer
completion(InfoModel(json: JSON as? Dictionary<String, Any>))
}
}
}
// call this function anywhere
parse(url: "") { (m:InfoModel) in
print("age= \(m.age), name= \(m.name)")
// now populate your label
labelOne.text = "\(m.age)"
labelTwo.text = name
}
You set the text property of the label in the completion block, basically:
func callAlamo(url: String){
Alamofire.request(url).responseJSON(completionHandler: {
response in
// here we say get me a non optional data (otherwise don't do the if)
if let data = response.data {
// here we are saying if you can't get me a value (i.e. not nil) for:
// json (note the try? will give nil if there is an error)
// name, we get the name out of the json dictionary
// then go to the else block, where we exit the function
// Happy case where we values for json and name we now have non optional variables W00t
guard
let json = try? self.parseData(JSONData: data),
let name = json["name"] as? String
else {
print("name does not exist in json: \(json)")
return
}
// Time to set the label
self.name.text = name
}
})
}
// Changed this to return JSON as a dictionary (it looks like what you printed was a dictionary)
// I also changed this so it throws the error and doesn't deal with it.
// It probably doesn't know what to do if it can't read json something
// else should handle the error higher up the stack
func parseData(JSONData: Data) throws -> [String: Any]? {
return try JSONSerialization.jsonObject(with:
JSONData, options: .mutableContainers) as? [String: Any]
}
NB: This is untested if your having problems and I'll go for a tested solution.
Edit: Answering how to get another property.
The way we got "name" was this chunk of code:
guard
let json = try? self.parseData(JSONData: data),
let name = json["name"] as? String
else {
print("name does not exist in json: \(json)")
return
}
To get another property out we could do this:
guard
let json = try? self.parseData(JSONData: data),
let name = json["name"] as? String,
let summonerLevel = json["summonerLevel"] as? Int
else {
print("name does not exist in json: \(json)")
return
}
Then to display summonerLevel we do the same as with name (although we have an int not a String)
// Time to set the label
self.name.text = name
// (you will have to create this new label)
self.summonerLevelLabel.text = "Level: \(summonerLevel)"
I'm trying to parse a JSON string returned from a new API. The returned JSON string looks like this.
QuerySearchResult":{
"StartAt":"1",
"Count":"40",
"TotalAvailable":"500",
"Items":[
{"TITLE":"OST420 Generation",
"PATH":"http:\\Test.pdf",
"WRITE":"2016-12-12T15:47:42",
"RANK":"32286574",
"SIZE":"145091",
"ISDOCUMENT":"true",
"ID":"18548",
"WPTASK":"Onsite Generation",
"WPDOCTYPE":"Local Operating Procedure",
"WPDOCREFID":"304580",
"WPCONTENTTYPE":"Document"},
{"TITLE":"OST420 Measurement",
"PATH":"http:\Test33.pdf",
.
.
I'm using the code below which accepts the JSON variable but fails when I try to load item. I've tried using Array around Dictionary but it still fails. What declaration do I need to read in Items?
if let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as? Dictionary<String, AnyObject> {
for item in json {
if let dict = item as? Dictionary<String, AnyObject> {
if let items = json["Items"] as? Array<Dictionary<String, AnyObject>> {
for rec in items {
if let title = rec["TITLE"] as? String {
let xx = title
}
}
}
}
}
First of all you are not correctly iterating through Dictionary also instead of looping through Dictionary for accessing single value, try by directly accessing it through subscripting and the proper JSON notation of Dictionary in Swift 3 is [String : Any].
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any],
let queryDic = json["QuerySearchResult"] as? [String : Any],
let items = queryDic["Items"] as? [[String : Any]] {
for item in items {
if let title = item["TITLE"] as? String {
print(title)
}
}
}
In Swift 3.1 :-
func parseJson(anyObj:AnyObject) {
if let anyObj = anyObj as? Array<Dictionary<String,AnyObject>> {
self.yourArrayName = anyObj.flatMap({yourModelName(json:$0) })
}
}
The Json returns the data into the dictionary and array format, so treat the json data like it, it makes you easier to understand.
Data in braces '{' and '}' is dictionary.
and Data in braces '[' or ']' is array.
Now start parsing the json data by the dictionary and array properties it exactly works. Or for confirmation print your json data.
Use SwiftyJSON : which can simply and easily parse the JSON more than your code.
So, how do we parse using that?
First get your response as Data()
let jsonData = JSON(data: results!)
Then here how we parse that JSON.
import Foundation
import SwiftyJSON
enum JSONParseError : ErrorType {
case UnknownField
case EmptyJSON
}
extension JSONParseError : CustomStringConvertible {
var description: String {
switch self {
case .UnknownField:
return "Error when parsing json because there is no field"
case .EmptyJSON:
return "Error when parsing empty json"
}
}
}
guard let querySearchResult : [String : JSON] = jsonData["QuerySearchResult"].dictionary else{
throw JSONParseError.UnknownField
}
guard let startAt : String = querySearchResult["StartAt"].string else{
throw JSONParseError.UnknownField
}
guard let count : String = querySearchResult["Count"].string else{
throw JSONParseError.UnknownField
}
guard let totalAvailable : String = querySearchResult["TotalAvailable"].string else{
throw JSONParseError.UnknownField
}
guard let items : [JSON] = querySearchResult["Items"].array else{
throw JSONParseError.UnknownField
}
if items.count > 0 {
for i in 0 ..< items.count{
guard let title = items[i]["TITLE"].string else{
throw JSONParseError.UnknownField
}.... //So On
}
}else{
throw JSONParseError.EmptyJSON
}