JSON Parsing in Swift 4 - json

I'm quite new to Swift and i am trying to return a string from my parsed JSON array. I'm not sure how i use the "arrayString" var in another class as i haven't "declared" it in the other class. I assume it has something to do with it being in URLSession. Any tips on what i could do?
struct Games: Decodable {
let videoLink: String
}
class BroadService: NSObject {
static let sharedInstance = BroadService()
func fetchBroadcasts(completion: #escaping ([Games]) -> ()) {
let jsonUrlString = "LINK IS HERE."
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let json = try JSONDecoder().decode([Games].self, from: data)
let arrayString = (json[linkcell].videoLink)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
This is my code at the minute, It's not the most neat thing in the world, but it gets the job done.

There are several issues with your code:
You needlessly extend NSObject.
You never call the completion block.
arrayString is not an array.
Your completion block should at least make the Games array optional to indicate an error.
Here's how your code should be:
struct Games: Decodable {
let videoLink: String
}
class BroadService {
static let sharedInstance = BroadService()
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
let jsonUrlString = "LINK IS HERE."
guard let url = URL(string: jsonUrlString) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
}
}
Then you can call it as follows:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
// Process all games if needed
for game in games {
let videoLink = game.videoLink
// do something
}
// Or access a specific game:
let game = games[someIndex]
let videoLink = game.videoLink
// do something
}
}

Related

Fetch data from nested JSON API in SwiftUI (object in array in object in another object)

Beginner here, in a bit over my head with this. ;)
I've found examples that have shown me how to get data from a JSON API feed if the feed is structured as an array of objects, but I don't know how to approach getting the data (specifically, url and title) if the data I'm retrieving comes back in a more complex nested structure like this one:
{
"races": {
"videos": [{
"id": 1,
"url": "firsturl",
"title": "1st Video Title"
}, {
"id": 2,
"url": "secondurl",
"title": "2nd Video Title"
}]
}
}
I've succeeded at get data from another API feed that's structured as a simple array of objects--it's like what's above but without the extra two lead-in objects, namely this: { "races": { "videos":
Here's the code I pieced together from a few examples that worked for the simple array:
import SwiftUI
struct Video: Codable, Identifiable {
public var id: Int
public var url: String
public var title: String
}
class Videos: ObservableObject {
#Published var videos = [Video]()
init() {
let url = URL(string: "https://exampledomain.com/jsonapi")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let videoData = data {
let decodedData = try JSONDecoder().decode([Video].self, from: videoData)
DispatchQueue.main.async {
self.videos = decodedData
}
} else {
print("no data found")
}
} catch {
print("an error occurred")
}
}.resume()
}
}
struct VideosView: View {
#ObservedObject var fetch = Videos()
var body: some View {
VStack {
List(fetch.videos) { video in
VStack(alignment: .leading) {
Text(video.title)
Text("\(video.url)")
}
}
}
}
}
I've spent several hours over a few days reading and watching tutorials, but so far nothing is sinking in to help me tackle the more complex JSON API feed. Any tips would be greatly appreciated!
UPDATE:
With the help of a Swift Playground tutorial and the suggested structs mentioned in the comments below, I've succeeded at retrieving the more complex data, but only in Swift Playgrounds, using this:
import SwiftUI
struct Welcome: Codable {
let races: Races
}
struct Races: Codable {
let videos: [Video]
}
struct Video: Codable {
let id: Int
let url, title: String
}
func getJSON<T: Decodable>(urlString: String, completion: #escaping (T?) -> Void) {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(T.self, from: data) else {
completion(nil)
return
}
completion(decodedData)
}.resume()
}
getJSON(urlString: "https://not-the-real-domain.123/api/") { (followers:Welcome?) in
if let followers = followers {
for result in followers.races.videos {
print(result.title )
}
}
}
Now, I'm struggling with how to properly integrate this Playgrounds snippet in to the working SwiftUI file's VideosViews, etc.
UPDATE 2:
import SwiftUI
struct Welcome: Codable {
let races: RaceItem
}
struct RaceItem: Codable {
let videos: [VideoItem]
}
struct VideoItem: Codable {
let id: Int
let url: String
let title: String
}
class Fetcher: ObservableObject {
func getJSON<T: Decodable>(urlString: String, completion: #escaping (T?) -> Void) {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(T.self, from: data) else {
completion(nil)
return
}
completion(decodedData)
}.resume()
}
}
struct JSONRacesView: View {
#ObservedObject var fetch = Fetcher()
getJSON(urlString:"https://not-the-real-domain.123/api/") { (followers:Welcome?) in
if let followers = followers {
for result in followers.races.videos {
print(result.title )
}
}
}
var body: some View {
VStack {
List(fetch.tracks) { track in
VStack(alignment: .leading) {
Text(track.title)
Text("\(track.url)")
}
}
}
}
There's a great site called QuickType (app.quicktype.io) where you can paste in some JSON and get the Swift structs generated for you. Here's what it gives you:
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let races: Races
}
// MARK: - Races
struct Races: Codable {
let videos: [Video]
}
// MARK: - Video
struct Video: Codable {
let id: Int
let url, title: String
}
They have a bug in their template generator that mangles the demo line (I've submitted a pull request that is merged but isn't live on the site at the time of this writing), but here's what it should look like:
let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
Using do/try so you can catch the errors, you can decode the data and reach the lower levels by doing:
do {
let welcome = try JSONDecoder().decode(Welcome.self, from: jsonData)
let videos = welcome.races.videos //<-- this ends up being your [Video] array
} catch {
//handle any errors
}
Update, based on your comments and updates:
You chose to go a little bit of a different route than my initial suggestion, but that's fine. The only thing that I would suggest is that you might want to deal with handling errors at some point rather than just returning nil in all of the completions (assuming you need to handle errors -- maybe it just not loading is acceptable).
Here's a light refactor of your code:
class Fetcher: ObservableObject {
#Published var tracks : [VideoItem] = []
private func getJSON<T: Decodable>(urlString: String, completion: #escaping (T?) -> Void) {
guard let url = URL(string: urlString) else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
return
}
guard let data = data else {
completion(nil)
return
}
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(T.self, from: data) else {
completion(nil)
return
}
completion(decodedData)
}.resume()
}
func fetchData() {
getJSON(urlString:"https://not-the-real-domain.123/api/") { (followers:Welcome?) in
DispatchQueue.main.async {
self.tracks = followers?.races.videos ?? []
}
}
}
}
struct JSONRacesView: View {
#StateObject var fetch = Fetcher()
var body: some View {
VStack {
List(fetch.tracks, id: \.id) { track in
VStack(alignment: .leading) {
Text(track.title)
Text("\(track.url)")
}
}
}.onAppear {
fetch.fetchData()
}
}
}
You can see that now Fetcher has a #Published property that will store the tracks ([VideoItem]). getJSON is still in fetcher, but now it's private just to show that it isn't meant to be called directly. But, now there's a new function called fetchData() that your view will call. When fetchData gets data back, it sets the #Published property to that data. I used the ?? operator to tell the compiler that if followers is nil, then just use [] instead. This is all in a DispatchQueue.main.async block because the URL call is probably not going to return on the main thread and we need to make sure to always update the UI on the main thread (Xcode will warn you about this at runtime if you update the UI on a different thread).
JSONRacesView calls fetchData in onAppear, which happens exactly when it sounds like it will.
Last thing to note is I used #StateObject instead of #ObservedObject. If you're not on iOS 14 or macOS 11 yet, you could use #ObservedObject instead. There are some differences outside the scope of this answer, but that are easily Google-able.

JSON Request for an UIImage from WordPress Post

I would like to download a picture of a json file online. This image should be displayed in a UIImageView.
At least I know that I need a JSON request but as I can bring the image in the ImageView I do not know
I've tried to download the image with a JSON request, but I have not managed it yet
My Code looks like this:
class HomeControllerTableView: UITableViewController{
var coursesPicture = [CoursePicture]()
var courseURL = [CourseURL]()
// MARK: - CoursePicture
struct CoursePicture: Codable {
let links: LinksPICTURE
enum CodingKeys: String, CodingKey {
case links = "_links"
}
}
// MARK: - Links
struct LinksPICTURE: Codable {
let wpFeaturedmedia: [WpFeaturedmedia]
enum CodingKeys: String, CodingKey {
case wpFeaturedmedia = "wp:featuredmedia"
}
}
// MARK: - WpFeaturedmedia
struct WpFeaturedmedia: Codable {
let href: String
}
struct CourseURL: Codable {
let guid: GUIDURL
}
// MARK: - GUID
struct GUIDURL: Codable {
let rendered: String
}
#IBOutlet weak var imageView: UIImageView!
var hello = ""
var hello2 = ""
override func viewDidLoad() {
super.viewDidLoad()
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleTopMargin, .flexibleBottomMargin]
imageView.contentMode = .scaleAspectFill
tableView.isScrollEnabled = false
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .clear
self.tableView.backgroundView = imageView
tableView.addSubview(imageView)
fetchJSON()
fetchJSONPicture()
request()
fetchJSONURL()
}
public func fetchJSONPicture() {
let urlString = "EXAMPLE_URL"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
self.coursesPicture = [try JSONDecoder().decode(CoursePicture.self, from: data)]
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}.resume()
}
public func fetchJSONURL() {
let urlString = hello
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
self.courseURL = [try JSONDecoder().decode(CourseURL.self, from: data)]
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
if let url = URL(string: hello2) {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
DispatchQueue.main.async {
self.imageView.image = UIImage(data: data)
}
}
}.resume()
}
}
}
func request() {
let coursePicture = coursesPicture[0]// Index out of Range error
let courseUrl = courseURL[0] // Index out of Range error
hello = coursePicture.links.wpFeaturedmedia[0].href
hello2 = courseUrl.guid.rendered
}
}
The JSON Request should bring me an URL from the JSON File and store it in a global variable. After that i use the global variable to get the URL from the Image.
The Picture that i would fetch is in a WordPress Post as contributing picture.
I would like to make this code as dynamic as possible but the problem is that i became an error that is called "Index out of range".
Can me help someone maybe?
Thanks

Swift Searching For Values inside values of Dictionary

I have a URL which my app fetches. it prints a dictionary with two keys but inside one of the keys is a lot of information I would like to get for my app.
The URL gets lots of information but not as a conventional dictionary.
this is a VERY simplified version:
["person":
name: John
height: 187, "fruit": colour: red
]
etc...
so I would just want to get the name of the person inside the key person but I am having trouble finding this.
Is there any way to do this? I have been trying JSON Parsing, for loops and I am stuck.
Edit:
it isn't a dictionary inside a dictionary. If you would like to see what I am working with. Just copy and paste this link. It is an example of what I am using. http://itunes.apple.com/lookup?bundleId=com.burbn.instagram
I would need just the seller name or just the currency etc.
Code to read the link and print it:
override func viewDidLoad() {
super.viewDidLoad()
fetchData { (dict, error) in
print(dict!)
}
}
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: link)!
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()
}
The data you are fetching is JSON. In order to use it, you will have to decode it. The recommended way is using JSONDecoder in Swift.
First you will have to define your model, which correspond to the data model, and make it conform to Codable protocol:
struct App: Codable {
var sellerName: String
// Alternatively, if you don't want to use an enum, you can use a String.
var currency: Currency
enum Currency: String, Codable {
case australianDollar = "AUD",
case britishPound = "GBP",
case euro = "EUR",
case hongKongDollar = "HKD",
case usDollar = "USD"
// Complete this with all the currency…
}
}
struct JSONResult: Codable {
var resultCount: Int
var results: [App]
}
Once this is done, you only have to edit your fetchData method so it returns an array App populated with the data you fetched.
Swift 4 version:
func fetchData(completion: #escaping (JSONResult?, Error?) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(nil, error)
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(result, nil)
} catch {
print(error)
completion(nil, error)
}
}
}
task.resume()
}
Swift 5 version using Result type:
func fetchData(completion: #escaping (Result<JSONResult, Error>) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(.success(result))
} catch {
print(error)
completion(.failure(error))
}
}
}
task.resume()
}
More information about JSONDecoder
Dictionary data is:
let dict = ["person": ["name": "John", "height": "187"], "fruit": ["colour": "red"]]
Suppose you need name of the person. So you can do it by the following way.
if let person = dict["person"], let name = person["name"] as? String {
print (name)
}

Writing a JSON serialization function

I'd like to make a function that takes in a few parameters and then outputs the data I need from a web API. Obviously a good deal of the time I'll need to customize it to suit the use case but just for fun I'm trying to figure out a super basic function the successfully parses JSON, as about half of the lines of code in the function below are generic error handling.
For example if I generally use something like
func getJSON(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(TopStoriesResponse.self, from: data)
self.storyData = response.results
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
The only three things that will change from case to case (again, in the most absolutely basic of scenarios) are the url link to the API, the Struct that I set up to look for the pieces of data I need, and the array that I output the results to once the data request is finished.
Could I trim the fat on that and do something like
func jsonFetcher(apiLink: String, structToDecode: String, arrayThatHoldsResponse: [String], completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = apiLink
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(structToDecode, from: data)
arrayThatHoldsResponse = response.results
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
I'm just not sure about the data types of structToDecode and arrayThatHoldsResponse (in the example function above I just using String as a placeholder), assuming they look like
Struct(s)
struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]
private enum CodingKeys: String, CodingKey {
case title
case abstract
case url
case multimedia
}
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
abstract = try container.decode(String.self, forKey: .abstract)
url = try container.decode(String.self, forKey: .url)
multimedia = (try? container.decode([Multimedia].self, forKey: .multimedia)) ?? []
}
}
Array
var storyData = [Story]()
This way I can just call
jsonFetcher(apiLink: link, structToDecode: myStruct, arrayThatHoldsResponse: myArray, completionHandler: <#T##(Bool) -> ()#>)
Thanks for any help!
The power of generics. You can make a generic function, where the parameter is the urlString. The T inheritance the Decodable protocol.
This way you can call this function everytime as long as your Model inheritance the Decodable protocol.
func fetchData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
}
guard let data = data else { return }
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(object)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
How to call the function:
struct User: Decodable { }
fetchData(urlString: "yourUrl") { (User: User) in
// Handle result
}
struct Animal: Decodable { }
fetchData(urlString: "yourUrl") { (animal: Animal) in
// Handle result
}
// Or if you want to fetch an array of users instead
fetchData(urlString: "yourUrl") { (users: [User]) in
// Handle result
}
In your case
var storiesData: [Story] = []
fetchData(urlString: "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808") { (stories: [Story] in
storiesData = stories
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Based on Jacob's answer I recommend to return also a possible error.
To keep the generic layout declare an – also generic – enum as return type
enum FetchResult<T> {
case success(T), failure(Error)
}
and return FetchResult with the passed static type
func fetchData<T: Decodable>(url: URL, completion: #escaping (FetchResult<T>) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {completion(.failure(error!)); return }
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(.success(object))
} catch {
completion(.failure(error))
}
}.resume()
}
and use it
let jsonUrl = URL(string: "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=••••••••••••••••••:1:73741808")!
fetchData(url: jsonUrl) { (result : FetchResult<TopStoriesResponse>) in
switch result {
case .success(let object): print(object) // do something with object
case .failure(let error): print(error) // handle the error
}
}

Swift passing method params Struct Decodable

,Swift 4 how can I pass Decodable Struct in method params and parse it in JSONDecoder()?
error:
Cannot invoke 'decode' with an argument list of type '(Decodable,
from: Data)'
struct JsonRespons: Codable {
let uid: String
let msisdn: String
let APK: String
let fname: String
let lname: String
}
struct JsonResponsError: Decodable {
let uid: String
let error: String
}
extension UIView {
func phoneAuth(serverApi path:String, jsonStruct:Codable){
let jsonUrlString = Globals.JOSN_API_URL + path
guard let url = URL(string: jsonUrlString) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard err == nil else {
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(jsonStruct.self, from: data)
self.handleJsonResult(resalt: result as AnyObject)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
func handleJsonResult(resalt:AnyObject){
print(resalt)
}
}
Adding Codable to the inheritance list for Landmark triggers an automatic conformance that satisfies all of the protocol requirements from Encodable and Decodable:
You can Use Codable
struct Landmark: Codable {
var name: String
var foundingYear: Int
// Landmark now supports the Codable methods init(from:) and encode(to:),
// even though they aren't written as part of its declaration.
}
Alternative solution is
func phoneAuth(serverApi path: String, Completion block: #escaping ((Data) -> ())) {
URLSession.shared.dataTask(with: URL(string: url)!) { (data, res, err) in
if let d = data {
block(d)
}
}.resume()
}
Call a Methods
phoneAuth(serverApi: "yourUrl") { (data) in
do {
let result = try JSONDecoder().decode(YourDecodable.self, from: data)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}
You don't have to pass it as a parameter, you can achieve decoding like below
extension UIView {
func phoneAuth(serverApi path:String){
let jsonUrlString = Globals.JOSN_API_URL + path
guard let url = URL(string: jsonUrlString) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard err == nil else {
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(JsonRespons.self, from: data)
self.handleJsonResult(resalt: result as AnyObject)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}