IOS/Swift/SwiftyJSON: Parse Nested JSON from IBM Watson/Bluemix API - ios

I am receiving some JSON from IBM Watson's tone analyzer API in the format shown below for a piece of text. What I would like to do is capture JSON in an object with a property and value for the main tones, eg. anger:.218 disgust:2.20 etc. for each section of text analyzed. As I analyze more pieces of text I want to add them to an array of these objects.
At this stage, I just want to do something with the api using SwiftyJSON but am confused by the syntax:
The following code, just prints out as nil:
let anger = JSON(value)["results"][0]["anger"].array?.map { json in
json["anger"].stringValue
}
print (anger)
Would appreciate guidance on how to actually grab the tones and their values.... Thanks in advance for any suggestions.
JSON looks like this:
{
"document_tone" = {
"tone_categories" = (
{
"category_id" = "emotion_tone";
"category_name" = "Emotion Tone";
tones = (
{
score = "0.218727";
"tone_id" = anger;
"tone_name" = Anger;
},
{
score = "0.210102";
"tone_id" = disgust;
"tone_name" = Disgust;
},
{
score = "0.060026";
"tone_id" = fear;
"tone_name" = Fear;
},
{
score = "0.076444";
"tone_id" = joy;
"tone_name" = Joy;
},
{
score = "0.176849";
"tone_id" = sadness;
"tone_name" = Sadness;
}
);
},
{
"category_id" = "language_tone";
"category_name" = "Language Tone";
tones = (
{
score = 0;
"tone_id" = analytical;
"tone_name" = Analytical;
},
{
score = 0;
"tone_id" = confident;
"tone_name" = Confident;
},
{
score = 0;
"tone_id" = tentative;
"tone_name" = Tentative;
}
);
},
{
"category_id" = "social_tone";
"category_name" = "Social Tone";
tones = (
{
score = "0.02278";
"tone_id" = "openness_big5";
"tone_name" = Openness;
},
{
score = "0.340597";
"tone_id" = "conscientiousness_big5";
"tone_name" = Conscientiousness;
},
{
score = "0.541852";
"tone_id" = "extraversion_big5";
"tone_name" = Extraversion;
},
{
score = "0.545246";
"tone_id" = "agreeableness_big5";
"tone_name" = Agreeableness;
},
{
score = "0.743194";
"tone_id" = "emotional_range_big5";
"tone_name" = "Emotional Range";
}
);
}
);
};
}

You can quickly and easily accomplish this with Codable structs in swift 4 and URLSession.
Here is a pure swift solution to your problem. The struct properties map to your json tree, and you might need to change the structs to match the json you're receiving. The json you posted above is in an invalid format otherwise I could have mapped it correctly.
struct Tone: Codable {
let score: Double
let tone_id: String
let tone_name: String
}
struct Category: Codable {
let category_id: String
let category_name: String
let tones: [Tone]
}
struct DocumentTone: Codable {
let toneCategories: [Category]
}
final class ToneService {
static func fetchTone(toneUrl: URL, _ completion: #escaping([DocumentTone]) -> Void) {
URLSession.shared.dataTask(with: toneUrl) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
// If your json returns a single object, use DocumentTone.self instead of [DocumentTone].self, as well change the completion
let tone = try decoder.decode([DocumentTone].self, from: data)
completion(tone)
} catch let error {
print(error.localizedDescription)
}
}.resume()
}
}

Related

Parsing data from Dictionary

Need to parse only the keys. Here keys are (10, 12, 2, 26). All these keys are changeable. Also, need to parse the fields array. Not getting the data(keys -10, 12, 2, 26) and can not access the fields array.
{
data = {
10 = {
fields = (
{
text = Activity;
value = 0;
},
{
text = Duration;
value = "0 hrs";
},
{
text = Achievement;
value = "0 hrs";
}
);
indicator = "+0%";
"indicator_color" = "#0ebb39-";
title = "Negotiation (Office)";
};
12 = {
fields = (
{
text = Activity;
value = 0;
},
{
text = Duration;
value = "0 hrs";
},
{
text = Achievement;
value = "0 hrs";
}
);
indicator = "+0%";
"indicator_color" = "#0ebb39-";
title = "Proposal Preparation";
};
2 = {
fields = (
{
text = Activity;
value = 0;
},
{
text = Duration;
value = "0 hrs";
},
{
text = Achievement;
value = "0 hrs";
}
);
indicator = "+0%";
"indicator_color" = "#0ebb39-";
title = "Departmental Work";
};
26 = {
fields = (
{
text = Activity;
value = 0;
},
{
text = Duration;
value = "0 hrs";
},
{
text = Achievement;
value = "0 times";
}
);
indicator = "+0%";
"indicator_color" = "#0ebb39-";
title = "External Meeting";
};
indicator = "+0%";
"indicator_color" = "#0ebb39-";
title = "Pre-sales (Office)";
};
};
"error_code" = 0;
message = "Dashboard Summary";
status = success;
success = 1;
}
Here is some of my code where I am trying to parse it using Alamofire:
Alamofire.request(url, method: .get, parameters: parameters,headers:headers).responseJSON { response in
print(parameters)
switch response.result{
case.success(let data):
print("Success")
print(response)
//response.result.value
if let dataDict = data as? [String: Any] {
print("Here is my dataDict -\(dataDict)")
guard let rootData = dataDict["data"] as? [String: Any] else {return}
}
case .failure(let error)://let error
print("dddd",error.localizedDescription)
}
}

Parsing a JsonObject in Swift 4 from an URL

It seems for me this is a very simple task, but even after a lot of researching and trying I can't get it working...
So I have for example this URL, for what I understand this is a api to an JSONObject?!
http://api.geekdo.com/api/images?ajax=1&gallery=all&nosession=1&objectid=127023&objecttype=thing&pageid=357&showcount=1&size=thumb&sort=recent
If I open this link in my browser I get the following result:
{"images":[{"imageid":"1567153","imageurl_lg":"https://cf.geekdo-images.com/images/pic1567153_lg.jpg","name":null,"caption":"White
power
tiles","numrecommend":"6","numcomments":"0","user":{"username":"manosdowns","avatar":"1","avatarfile":"avatar_id33829.jpg"},"imageurl":"https://cf.geekdo-images.com/6fCr14v025ZKYhXRMnbhYR16Ta8=/fit-in/200x150/pic1567153.jpg"}],"config":{"sorttypes":[{"type":"hot","name":"Hot"},{"type":"recent","name":"Recent"}],"numitems":402,"endpage":402,"galleries":[{"type":"all","name":"All"},{"type":"game","name":"Game"},{"type":"people","name":"People"},{"type":"creative","name":"Creative"}],"categories":[{"type":"","name":"All"},{"type":"BoxFront","name":"BoxFront"},{"type":"BoxBack","name":"BoxBack"},{"type":"Components","name":"Components"},{"type":"Customized","name":"Customized"},{"type":"Play","name":"Play"},{"type":"Miscellaneous","name":"Miscellaneous"},{"type":"Mature","name":"Mature"},{"type":"uncat","name":"Uncategorized"}],"licensefilters":[{"type":"","name":"Any"},{"type":"reuse","name":"Copying
allowed"},{"type":"commercial","name":"Commercial use
allowed"},{"type":"modify","name":"Modification
allowed"}],"datefilters":[{"value":"alltime","name":"All
Time"},{"value":"today","name":"Today"},{"value":"twodays","name":"Two
Days"},{"value":"last7","name":"Last 7
Days"},{"value":"last30","name":"Last 30
Days"},{"value":"year","name":"Last 365
Days"}],"filters":[{"name":"Licenses","listname":"licensefilters","type":"licensefilter"},{"name":"Category","listname":"categories","type":"tag"},{"name":"Gallery","listname":"galleries","type":"gallery"}]}}
Now my first attempt was to parse this link the way I parse homepages:
guard let myURL = URL(string: link) else { > print("Error: \(link) doesn't seem to be a valid URL")
return
}
do {
link = try String(contentsOf: myURL, encoding: .ascii)
} catch let error {
print("Error: \(error)")
}
But this doesn't work as I now understand that's because this is JSON coded?!
I searched for parsing JSON and found some explanations for encoding and decoding, but my problem is that in all examples given, the explanations start by "having" already the contents of the JsonObject.
My problem is that I can read the contents of the URL in the browser but I would need the content of the URL in Xcode itself, so I can parse it?!
So in my specific case I would only need the content of "imageurl_lg"
...I would know how to do it if I could display the content I can see in my browser also in Xcode - but how do I get the contents of the link into Xcode?
For reference, I also read following instructions, but couldn't apply them to my example...
https://www.raywenderlich.com/172145/encoding-decoding-and-serialization-in-swift-4
https://grokswift.com/json-swift-4/
and some more but they didn't help me...
You need to use a URLSession task to do this, and after that you need to use JSONSerialization in this example I return a dictionary of [String:Any] you can convert it to whatever Model you need
Use this Code
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: "http://api.geekdo.com/api/images?ajax=1&gallery=all&nosession=1&objectid=127023&objecttype=thing&pageid=357&showcount=1&size=thumb&sort=recent")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
How use it
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
fetchData { (dict, error) in
debugPrint(dict)
}
}
Result Log printed
Optional(["config": {
categories = (
{
name = All;
type = "";
},
{
name = BoxFront;
type = BoxFront;
},
{
name = BoxBack;
type = BoxBack;
},
{
name = Components;
type = Components;
},
{
name = Customized;
type = Customized;
},
{
name = Play;
type = Play;
},
{
name = Miscellaneous;
type = Miscellaneous;
},
{
name = Mature;
type = Mature;
},
{
name = Uncategorized;
type = uncat;
}
);
datefilters = (
{
name = "All Time";
value = alltime;
},
{
name = Today;
value = today;
},
{
name = "Two Days";
value = twodays;
},
{
name = "Last 7 Days";
value = last7;
},
{
name = "Last 30 Days";
value = last30;
},
{
name = "Last 365 Days";
value = year;
}
);
endpage = 402;
filters = (
{
listname = licensefilters;
name = Licenses;
type = licensefilter;
},
{
listname = categories;
name = Category;
type = tag;
},
{
listname = galleries;
name = Gallery;
type = gallery;
}
);
galleries = (
{
name = All;
type = all;
},
{
name = Game;
type = game;
},
{
name = People;
type = people;
},
{
name = Creative;
type = creative;
}
);
licensefilters = (
{
name = Any;
type = "";
},
{
name = "Copying allowed";
type = reuse;
},
{
name = "Commercial use allowed";
type = commercial;
},
{
name = "Modification allowed";
type = modify;
}
);
numitems = 402;
sorttypes = (
{
name = Hot;
type = hot;
},
{
name = Recent;
type = recent;
}
); }, "images": <__NSSingleObjectArrayI 0x600000010710>( {
caption = "White power tiles";
imageid = 1567153;
imageurl = "https://cf.geekdo-images.com/6fCr14v025ZKYhXRMnbhYR16Ta8=/fit-in/200x150/pic1567153.jpg";
"imageurl_lg" = "https://cf.geekdo-images.com/images/pic1567153_lg.jpg";
name = "<null>";
numcomments = 0;
numrecommend = 6;
user = {
avatar = 1;
avatarfile = "avatar_id33829.jpg";
username = manosdowns;
}; } ) ])
Updated fixing "App Transport Security has blocked a cleartext" error
Adjust your info.plist
So this is what playground gave me.
import UIKit
var str = "Hello, playground"
func makeGetCall() {
guard let myURL = URL(string: "http://api.geekdo.com/api/images?ajax=1&gallery=all&nosession=1&objectid=127023&objecttype=thing&pageid=357&showcount=1&size=thumb&sort=recent") else {
print("Error: \(link) doesn't seem to be a valid URL")
return
}
do {
var content = try String(contentsOf: myURL, encoding: .ascii)
print("Content: \(content)")
} catch let error {
print("Error: \(error)")
}
}
makeGetCall()
Prints

Mapping two kind of responses with Alamofire and AlamofireObjectMapper (iOS - swift)

So, I'm making an iOS app the search a movie on imdb database (using omdb api) and the user could save the favorites.
To search by imdbTitle the request url is "https://www.omdbapi.com/?s=(imdbTitle)&type=movie", if imdbTitle = "arq", for example, the response is:
{
Response = True;
Search = (
{
Poster = "https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxODQ2MzkyMV5BMl5BanBnXkFtZTgwNjU3MTE5OTE#._V1_SX300.jpg";
Title = ARQ;
Type = movie;
Year = 2016;
imdbID = tt5640450;
},
{
Poster = "N/A";
Title = Arq;
Type = movie;
Year = 2011;
imdbID = tt2141601;
},
{
Poster = "N/A";
Title = "A.R.Q.";
Type = movie;
Year = 2015;
imdbID = tt3829612;
}
);
totalResults = 3;
}
But for save the movie I have to search by imdbID, url: "https://www.omdbapi.com/?i=(imdbID)", if imdbID is the one from the movie searched, imdbID = tt3829612, and the response is:
{
Actors = "Robbie Amell, Rachael Taylor, Shaun Benson, Gray Powell";
Awards = "N/A";
BoxOffice = "N/A";
Country = "USA, Canada";
DVD = "16 Sep 2016";
Director = "Tony Elliott";
Genre = "Sci-Fi, Thriller";
Language = English;
Metascore = "N/A";
Plot = "Trapped in a lab and stuck in a time loop, a disoriented couple fends off masked raiders while harboring a new energy source that could save humanity.";
Poster = "https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxODQ2MzkyMV5BMl5BanBnXkFtZTgwNjU3MTE5OTE#._V1_SX300.jpg";
Production = Netflix;
Rated = "N/A";
Ratings = (
{
Source = "Internet Movie Database";
Value = "6.4/10";
},
{
Source = "Rotten Tomatoes";
Value = "60%";
}
);
Released = "16 Sep 2016";
Response = True;
Runtime = "88 min";
Title = ARQ;
Type = movie;
Website = "N/A";
Writer = "Tony Elliott";
Year = 2016;
imdbID = tt5640450;
imdbRating = "6.4";
imdbVotes = "17,481";
}
So, my Movie class has to have the following attributes: poster, title, runtime, genre, director, actors, plot, released, imdbID and imdbRating (to show this on my UI View, except the imdbID, of course)
I'm a beginner and I'm too confused by all those things (never worked with API data before).
Anyways, after a lot of search I found that there is a way to get this response as an Array using AlamofireObjectMapper.
I already have my request functions:
func searchMoviesOnJson(imdbTitle: String, completionHandler: #escaping (Dictionary<String, Any>?) -> ()) {
let urlByName: String = "https://www.omdbapi.com/?s=\(imdbTitle)&type=movie"
//returns a list of movies that contains the title searched
//------------------------------------------------
Alamofire.request(urlByName).responseJSON {
response in
switch response.result {
case .success(let value):
let moviesJSON = value
completionHandler(moviesJSON as? Dictionary<String, Any>)
case .failure(_):
completionHandler(nil)
}
}
//------------------------------------------------
}
func getMovieFromJson(imdbID: String, completionHandler: #escaping (Dictionary<String, String>) -> ()) {
let urlById: String = "https://www.omdbapi.com/?i=\(imdbID)"
//returns a single movie information
//------------------------------------------------
Alamofire.request(urlById).responseJSON {
response in
if let moviesJSON = response.result.value {
completionHandler(moviesJSON as! Dictionary<String, String>)
}
}
//------------------------------------------------
}
So, could anyone explain to me (like you'll explain to a child) how can I do this mapping? I created a Movie.swift and in this file I'll put my class Movie and class MovieDAO.
How can I implement this two classes using AlamofireObjectMapper and who I have to change in my request methods?
Update:
class Movie {
let poster: String?
let title: String?
let runtime: String?
let genre: String?
let director: String?
let actors: String?
let plot: String?
let released: String?
let imdbID: String?
let imdbRating: String?
init(poster: String?, title: String?, runtime: String?, genre: String?, director: String?, actors: String?, plot: String?, released: String?, imdbID: String?, imdbRating: String?) {
//checking if is nil
if let isPoster = poster {
self.poster = isPoster
} else {
self.poster = nil
}
if let isTitle = title {
self.title = isTitle
} else {
self.title = nil
}
if let isGenre = genre {
self.genre = isGenre
} else {
self.genre = nil
}
if let isRuntime = runtime {
self.runtime = isRuntime
} else {
self.runtime = nil
}
if let isDirector = director {
self.director = isDirector
} else {
self.director = nil
}
if let isActors = actors {
self.actors = isActors
} else {
self.actors = nil
}
if let isPlot = plot {
self.plot = isPlot
} else {
self.plot = nil
}
if let isReleased = released {
self.released = isReleased
} else {
self.released = nil
}
if let isImdbID = imdbID {
self.imdbID = isImdbID
} else {
self.imdbID = nil
}
if let isImdbRating = imdbRating {
self.imdbRating = isImdbRating
} else {
self.imdbRating = nil
}
}
}
UPDATE 2
I declare the variable movieDetail as you said to do, but the xcode showed an error and suggesting that I could add the parameters, so I did and its not solved.
You will have to parse the JSON received:
For e.g. if json received is:
{
Response = True;
Search = (
{
Poster = "https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxODQ2MzkyMV5BMl5BanBnXkFtZTgwNjU3MTE5OTE#._V1_SX300.jpg";
Title = ARQ;
Type = movie;
Year = 2016;
imdbID = tt5640450;
},
{
Poster = "N/A";
Title = Arq;
Type = movie;
Year = 2011;
imdbID = tt2141601;
},
{
Poster = "N/A";
Title = "A.R.Q.";
Type = movie;
Year = 2015;
imdbID = tt3829612;
}
);
totalResults = 3;
}
As you have mentioned, if you have stored the json response in NSDictionary
You can access the values of Search results as
var moviesArray = [Movie]() //array of your Movie class
var imdbIdArray = [String]()
if let searchResults = moviesJSON["Search"] as? NSArray {
for searchResult in searchResults {
let movieResult = searchResult as! Dictionary<String,String>
let movieDetail = Movie()
movieDetail.title = movieResult["Title"]
movieDetail.type = movieResult["Type"]
//similarly you can do it for other params as well
moviesArray.append(movieDetail)
let imdbId = movieResult["imdbId"]
imdbIdArray.append(imdbId)
}
}
class Movie: NSObject {
var title = String()
var type = String()
}
Now moviesArray consist of list of movies returned in the result set.

SwiftyJSON parse json query

I am writing a code in swift 3 for parse a query in json format result from http request.
the json format is:
JSON: {
base = stations;
coord = {
lat = "23.9";
lon = "42.89";
};
weather = (
{
description = mist;
icon = 50n;
id = 701;
main = Mist;
},
{
description = fog;
icon = 50n;
id = 741;
main = Fog;
}
);
wind = {
deg = "222.506";
speed = "1.72";
};}
My code is:
Alamofire.request(url).responseJSON { response in
if let a = response.result.value {
let jsonVar = JSON(a)
if let resDati = jsonVar["base"].string {
print(resDati as String) // <- OK
}
if let dati2 = jsonVar["weather"].array {
for item in dati2 {
print(" > \(item["main"])") // <- OK
}
}
} else {
print(Error.self)
}
}
The problem is on "coord" and "wind" data i have try:
if let dati4 = jsonVar["wind"].array {
for item in dati4 {
print("-- \(item)")
} }
I cannot print the data relatives to "wind" and "coord" in json format.
How can I resolve this.
Thank you.
The key wind contains a dictionary, not an array, you can get the deg and speed values using SwiftyJSON with this code:
if let wind = jsonVar["wind"].dictionary,
let deg = wind["deg"]?.double,
let speed = wind["speed"]?.double {
print(deg, speed)
}
coord works accordingly
if let coord = jsonVar["coord"].dictionary,
let lat = coord["lat"]?.double,
let lon = coord["lon"]?.double {
print(lat, lon)
}
Note: All values are of type Double, the json format is misleading.

How to get dictionary objects in array

I have a dict(NSDictionary) that has the following data.
{
images = (
{
image = "image.jpg";
scores = (
{
"classifier_id" = "Adventure_Sport";
name = "Adventure_Sport";
score = "0.662678";
},
{
"classifier_id" = Climbing;
name = Climbing;
score = "0.639987";
},
{
"classifier_id" = Flower;
name = Flower;
score = "0.628092";
},
{
"classifier_id" = "Nature_Scene";
name = "Nature_Scene";
score = "0.627548";
},
{
"classifier_id" = Icicles;
name = Icicles;
score = "0.617094";
},
{
"classifier_id" = Volcano;
name = Volcano;
score = "0.604928";
},
{
"classifier_id" = Potatoes;
name = Potatoes;
score = "0.602799";
},
{
"classifier_id" = "Engine_Room";
name = "Engine_Room";
score = "0.595812";
},
{
"classifier_id" = Bobsledding;
name = Bobsledding;
score = "0.592521";
},
{
"classifier_id" = White;
name = White;
score = "0.587923";
},
{
"classifier_id" = Yellow;
name = Yellow;
score = "0.574398";
},
{
"classifier_id" = "Natural_Activity";
name = "Natural_Activity";
score = "0.54574";
},
{
"classifier_id" = Butterfly;
name = Butterfly;
score = "0.526803";
},
{
"classifier_id" = "Dish_Washer";
name = "Dish_Washer";
score = "0.513662";
},
{
"classifier_id" = Rainbow;
name = Rainbow;
score = "0.511032";
}
);
}
);
}
I am not able to figure out a way to access classfier_id in an array
Really need your help. Thanks. PS I have already tried dict["scores"] and dict["image.scores"]
Kindly help.. Thanks
You want
let classifier_ids = dict["scores"].map{$0["classifier_id"]}
If your containers are NSObjects rather than Swift Dictionaries and Arrays then you will need to add some type-casting, but that's the basic idea.
This code would be safer for non-typed collections:
var classifier_ids: [String?]
if let array = dict["scores"] as? [String:String]
{
let classifier_ids = array.map{$0["classifier_id"]}
}
That should give you an array of optionals, where a given entry will be nil if that entry in the array did not contain a "classifier_id" key/value pair.
Or if you want to skip entries that don't contain a "classifier_id" key/value pair:
var classifier_ids: [String]
if let array = dict["scores"] as? [String:String]
{
let classifier_ids = array.flatmap{$0["classifier_id"]}
}
Taking it a step at at time:
if let array = dict["images"] as? NSArray {
if let array2 = array[0]["scores"] as? NSArray {
if let ids = array2.valueForKey("classifier_id") as? [String] {
// use array of ids
print(ids)
}
}
}
or all in one shot:
if let ids = (dict["images"]?[0]["scores"])?.valueForKey("classifier_id") as? [String] {
// use array of ids
print(ids)
}

Resources