Correct parse JSON in swift - json

This is JSON what I want to parse and use in my app
{
"status": "ok",
"source": "the-next-web",
"sortBy": "latest",
-"articles": [
-{
"author": "Mix",
"title": "Social media giants like Facebook liable to €50M ‘hate speech’ fines in Germany",
"description": "Germany will soon punish social media giants like Facebook, Twitter and YouTube with fines of up to €50 million (approximately $57 million) if they fail to take down illegal or offensive ...",
"url": "https://thenextweb.com/facebook/2017/06/30/facebook-youtube-twitter-germany-50m/",
"urlToImage": "https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2017/03/facebook.jpg",
"publishedAt": "2017-06-30T15:10:58Z"
},
-{
"author": "Abhimanyu Ghoshal",
"title": "Google’s new Android app makes it easy to save mobile data on the go",
"description": "The good folks at Android Police have spotted a new app from Google called Triangle; it lets you control which other apps can use your mobile data. It's a handy little tool if you're ...",
"url": "https://thenextweb.com/apps/2017/06/30/googles-new-android-app-makes-it-easy-to-save-mobile-data-on-the-go/",
"urlToImage": "https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2017/06/Triangle-hed.jpg",
"publishedAt": "2017-06-30T13:16:12Z"
},
-{
This is my methods to parse the Json:
typealias GetWeatherSuccess = (_ result: NSDictionary) -> Void
typealias GetWeatherFailure = (_ error: Error?) -> Void
func requestNewsForToday() -> URLRequest {
let urlText = NewsManager.shared.weatherRequest()
return URLRequest(url: URL(string: urlText)! as URL)
}
func getNews(successHandler: GetWeatherSuccess?,
failureHandler: GetWeatherFailure?) {
let session = URLSession.shared
let dataTask = session.dataTask(with: requestNewsForToday()) { data, _, error in
if error == nil {
if let dic = try? JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
successHandler?(dic!)
}
} else {
failureHandler?(error)
}
}
dataTask.resume()
}
And usage of methods:
NetworkManager.shared.getNews(successHandler: { (json: NSDictionary) in
let articles = json.object(forKey: "articles") as! NSArray
print(articles[0])
}) { (_:Error?) in
print("error")
}
}
And result is oblivious:
{
author = "Romain Dillet";
description = "\U201cWe called this building Station F, like Station France, Station Femmes [ed. note: women in French], Station Founders or Station Freyssinet because..";
publishedAt = "2017-07-01T09:47:38Z";
title = "A walk around Station F with Emmanuel\U00a0Macron";
url = "https://techcrunch.com/2017/07/01/a-walk-around-station-f-with-emmanuel-macron/";
urlToImage = "https://tctechcrunch2011.files.wordpress.com/2017/06/station-f-emmanuel-macron-9.jpg?w=764&h=400&crop=1";
}
How to take authors String for example?
Or make full array of data from json and use it?

I am going to use Swift 4 Decodable protocol to parse JSON the simple and easy way! It's really a very powerful tool that Apple has given in this update!
By looking at the JSON, I can infer I need two structs which will conform to the Decodable protocol:-
struct jsonData: Decodable {
let status: String
let source: String
let sortBy: String
let articles: [Articles]
}
struct Articles: Decodable {
let author: String
let title: String
let description: String
let url: String
let urlToImage: String
let publishedAt: String
}
Pay attention that I have exactly named the variables same as your keys in the JSON. This is where the Decodable protocol magic happens. It automatically infers which key value/pair belongs to which variable on the basis of your variable name (meaning your key name in JSON, should be the variable name in swift), provided you named them correctly, otherwise it will throw an error.
Now, making a network connection as usual:-
URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do {
let item = try JSONDecoder().decode(jsonData.self, from: data)
print(item.status) // prints "ok"
print(items.articles) // prints the 2 arrays in the json, provided in your Question
}
catch let jsonErr {
print("serialisation error", jsonErr)
}
}

Related

Trouble Decoding JSON Data with Swift

Trying to get a little practice in decoding JSON data, and I am having a problem. I know the URL is valid, but for some reason my decoder keeps throwing an error. Below is my model struct, the JSON object I'm trying to decode, and my decoder.
Model Struct:
struct Event: Identifiable, Decodable {
let id: Int
let description: String
let title: String
let timestamp: String
let image: String
let phone: String
let date: String
let locationline1: String
let locationline2: String
}
struct EventResponse: Decodable {
let request: [Event]
}
JSON Response:
[
{
"id": 1,
"description": "Rebel Forces spotted on Hoth. Quell their rebellion for the Empire.",
"title": "Stop Rebel Forces",
"timestamp": "2015-06-18T17:02:02.614Z",
"image": "https://raw.githubusercontent.com/phunware-services/dev-interview-homework/master/Images/Battle_of_Hoth.jpg",
"date": "2015-06-18T23:30:00.000Z",
"locationline1": "Hoth",
"locationline2": "Anoat System"
},
{
"id": 2,
"description": "All force-sensitive members of the Empire must report to the Sith Academy on Korriban. Test your passion, attain power, to defeat your enemy on the way to becoming a Dark Lord of the Sith",
"title": "Sith Academy Orientation",
"timestamp": "2015-06-18T21:52:42.865Z",
"image": "https://raw.githubusercontent.com/phunware-services/dev-interview-homework/master/Images/Korriban_Valley_TOR.jpg",
"phone": "1 (800) 545-5334",
"date": "2015-09-27T15:00:00.000Z",
"locationline1": "Korriban",
"locationline2": "Horuset System"
},
{
"id": 3,
"description": "There is trade dispute between the Trade Federation and the outlying systems of the Galactic Republic, which has led to a blockade of the small planet of Naboo. You must smuggle supplies and rations to citizens of Naboo through the blockade of Trade Federation Battleships",
"title": "Run the Naboo Blockade",
"timestamp": "2015-06-26T03:50:54.161Z",
"image": "https://raw.githubusercontent.com/phunware-services/dev-interview-homework/master/Images/Blockade.jpg",
"phone": "1 (949) 172-0789",
"date": "2015-07-12T19:08:00.000Z",
"locationline1": "Naboo",
"locationline2": "Naboo System"
}
]
My Decoder:
func getEvents(completed: #escaping (Result<[Event], APError>) -> Void) {
guard let url = URL(string: eventURL) else {
completed(.failure(.invalidURL))
return
}
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, response, error in
if let _ = error {
completed(.failure(.unableToComplete))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(.invalidResponse))
return
}
guard let data = data else {
completed(.failure(.invalidData))
return
}
do {
let decoder = JSONDecoder()
let decodedResponse = try decoder.decode(EventResponse.self, from: data)
completed(.success(decodedResponse.request))
} catch {
completed(.failure(.invalidData))
}
}
task.resume()
}
I am sure the answer is pretty obvious to some but I have been beating my head against a wall. Thanks.
The EventResponse suggests that the JSON will be of the form:
{
"request": [...]
}
But that is obviously not what your JSON contains.
But you can replace:
let decodedResponse = try decoder.decode(EventResponse.self, from: data)
With:
let decodedResponse = try decoder.decode([Event].self, from: data)
And the EventResponse type is no longer needed.
FWIW, in the catch block, you are returning a .invalidData error. But the error that was thrown by decode(_:from:) includes meaning information about the parsing problem. I would suggest capturing/displaying that original error, as it will tell you exactly why it failed. Either print the error message in the catch block, or include the original error as an associated value in the invalidData error. But as it stands, you are discarding all of the useful information included in the error thrown by decode(_:from:).
Unrelated, but you might change Event to use URL and Date types:
struct Event: Identifiable, Decodable {
let id: Int
let description: String
let title: String
let timestamp: Date
let image: URL
let phone: String
let date: Date
let locationline1: String
let locationline2: String
}
And configure your date formatted to parse those dates for you:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0) // not necessary because you have timezone in the date string, but useful if you ever use this formatter with `JSONEncoder`
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
And if you want to have your Swift code follow camelCase naming conventions, even when the API does not, you can manually specify your coding keys:
struct Event: Identifiable, Decodable {
let id: Int
let description: String
let title: String
let timestamp: Date
let image: URL
let phone: String
let date: Date
let locationLine1: String
let locationLine2: String
enum CodingKeys: String, CodingKey {
case id, description, title, timestamp, image, phone, date
case locationLine1 = "locationline1"
case locationLine2 = "locationline2"
}
}

How to decode JSON response from API in SwiftUI to use in a view

I'm pretty new to developing apps and working with Xcode and Swift and trying to get a handle on how to decode JSON responses from an API. I'm also trying to follow MVVM practices.
I'm calling to an API that returns data structured like this:
"location": {
"latitude": -27.4748,
"longitude": 153.017 },
"date": "2020-12-21",
"current_time": "21:55:42.198",
"sunrise": "04:49",
"sunset": "18:42",
"...": "..."
}
My understanding is I need a struct to decode this information into. This is what my struct looks like (it may be incorrect):
struct Example: Decodable {
let location: Coordinates
let date: String
let current_time: String
let sunset: String
let sunset: String
let ... :String
struct Coordinates: Decodable{
let latitude: Double
let longitude: Double
}
}
So I want to be able to call this api. I have the correct address to call it because the dashboard on the website I'm using is showing I've hit it. Could I get some guidance on how to call it and decode the response? Currently I'm doing something like this:
if let url = URL(string: "this is the api web address"){
URLSession.shared.dataTask(with: url){ (with: data, options [] ) as?
[String:String]{
print("(\json)" + "for debugging purposes")
}}
else{
print("error")
}
.resume()
Thanks for any and all help. Again, my goal is to call this function, hit the API, decode the JSON response and store it into a variable I can use in my views elsewhere. Thanks!
You are mixing JSONSerialization with Codable and URLSession. Forget about JSONSerialization. Try to focus on Codable protocol.
struct Example: Decodable {
let location: Coordinates
let date: String
let currentTime: String
let sunrise: String
let sunset: String
}
struct Coordinates: Decodable {
let latitude: Double
let longitude: Double
}
This is how you can decode your json data synchronously
extension JSONDecoder {
static let shared: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
return decoder
}()
}
extension Data {
func decodedObject<T: Decodable>() throws -> T {
try JSONDecoder.shared.decode(T.self, from: self)
}
}
Playground testing
let json = """
{
"location": {
"latitude": -27.4748,
"longitude": 153.017 },
"date": "2020-12-21",
"current_time": "21:55:42.198",
"sunrise": "04:49",
"sunset": "18:42",
}
"""
let data = Data(json.utf8)
do {
let example: Example = try data.decodedObject()
print(example)
} catch {
print(error)
}
And fetching your data asynchronously
extension URL {
func getResult<T: Decodable>(completion: #escaping (Result<T, Error>) -> Void) {
URLSession.shared.dataTask(with: self) { data, response, error in
guard let data = data, error == nil else {
completion(.failure(error!))
return
}
do {
completion(.success(try data.decodedObject()))
} catch {
completion(.failure(error))
}
}.resume()
}
}
let url = URL(string: "https://www.example.com/whatever")!
url.getResult { (result: Result<Example, Error>) in
switch result {
case let .success(example):
print(example)
case let .failure(error):
print(error)
}
}
If you need help with your SwiftUI project implementing MVVM feel free to open a new question.
Correct Method to decode a local JSON file in SwiftUI
do {
let path = Bundle.main.path(forResource: "filename", ofType: "json")
let url = URL(fileURLWithPath: path!)
let data = try Data(contentsOf: url)
let decodedresults = try JSONDecoder().decode(struct_name.self, from: data)
print(decodedresults)
} catch {
print(error)
}

Accessing JSON Array from API response in Swift

I am brand new to writing swift so any tips on improvement/best practices are welcome but my main issue is that I am having trouble accessing a nested JSON array list. I am using this free API and trying to show a list of characters https://swapi.dev/api/people/
Please see the code snippet below.
When I print the type its : Optional<Any> and when i print json["results"] it prints the array like:
Optional(<__NSArrayI 0x600000fe31e0>(
{
"birth_year" = 19BBY;
created = "2014-12-09T13:50:51.644000Z";
....
I have tried several different things but have been unsuccessful. Could someone please give some advice on how I might iterate the list under json["results"?
func onLoad() -> Void {
let url = URL(string: "https://swapi.dev/api/people")
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a simple String
if let data = data {
// let json = try? JSONSerialization.jsonObject(with: data, options: [])
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
// try to read out a string array
print(type(of: json["results"]))
print(json["results"])
}
} catch let error as Error {
print("Failed to load: \(error.localizedDescription)")
}
}
}
task.resume()
}
Thanks for any help!
You should really be using Decodable rather than trying to parse it with JSON as that can easily lead to errors as you are accessing values by strings and it doesn't allow the IDE to help you.
You need to create some objects that describe what you are getting in your response.
Your main json response is made up of the following
{
"count": 82,
"next": "http://swapi.dev/api/people/?page=2",
"previous": null,
"results": [...]
}
This allows you to create a People struct that conforms to Decodable.
struct People: Decodable {
let count: Int
let next: URL?
let previous: URL?
let results: [Person]
}
The results array is really what you are after as that contains all the information about a person.
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "http://swapi.dev/api/planets/1/",
"films": [
"http://swapi.dev/api/films/1/",
"http://swapi.dev/api/films/2/",
"http://swapi.dev/api/films/3/",
"http://swapi.dev/api/films/6/"
],
"species": [],
"vehicles": [
"http://swapi.dev/api/vehicles/14/",
"http://swapi.dev/api/vehicles/30/"
],
"starships": [
"http://swapi.dev/api/starships/12/",
"http://swapi.dev/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "http://swapi.dev/api/people/1/"
}
We can represent this with the following struct called Person that also conforms to Decodable
struct Person: Decodable {
let name: String
let height: String
let mass: String
let hairColor: String
let skinColor: String
let birthYear: String
let gender: Gender
let homeworld: String
let films: [URL]
let species: [URL]
let vehicles: [URL]
let starships: [URL]
let created: Date
let edited: Date
let url: URL
}
enum Gender: String, Decodable {
case male
case female
case unknown = "n/a"
}
Note a couple of differences between the names in the struct and the names in the object that you are getting back. eg hair_color (snakecase) and hairColor (camelCase) In Swift it is common to write it the latter way and when we use decodable we can tell our decoder to use a custom key decoding strategy. Also note that I have used an enum for Gender. This isn't required and we could have just used a String. Also note that created and edited are Dates, however they are not iso8601 compliant but we can also specify a custom date decoding strategy.
Here is how we can decode the data that you have received.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let people = try decoder.decode(People.self, from: data)
Now we can put this all together in your network request to get the following:
func onLoad() {
let url = URL(string: "https://swapi.dev/api/people")
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a simple String
if let data = data {
do {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let people = try decoder.decode(People.self, from: data)
people.results.forEach { person in print(person) }
} catch {
print("Failed to load: \(error)")
}
}
}
task.resume()
}
Cast results as an Array of Dictionary. Here's how
if let data = data {
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let results = json["results"] as? [[String: Any]] {
for result in results {
print(result)
}
}
} catch {
print("Failed to load: \(error.localizedDescription)")
}
}
Better Approach: Use Codable, JSONSerialization feels bit outdated.
Related Links:
https://developer.apple.com/documentation/swift/codable
https://www.swiftbysundell.com/basics/codable/

Why I can't parse the following json?

I have json response that I need to parse that, according to the https://newsapi.org web site, should look like:
{
"status": "ok",
"totalResults": 4498,
"articles": [{
"source": {
"id": null,
"name": "Techweekeurope.co.uk"
},
"author": null,
"title": "Top ten cloud service providers for reliability and price",
"description": "In a time where the reliability and stability of cloud service providers comes into spotlight, we pick our top ten that will help you propel your business to the next level. We recently talked about building your own local network with a Raspberry Pi starter …",
"url": "https://www.techweekeurope.co.uk/top-ten-cloud-service-providers-reliability-price/",
"urlToImage": "https://www.techweekeurope.co.uk/wp-content/uploads/2020/01/Cloud-Service-Providers.gif",
"publishedAt": "2020-01-04T16:17:00Z",
"content": "In a time where the reliability and stability of cloud service providers comes into spotlight, we pick our top ten that will help you propel your business to the next level. We recently talked about building your own local network with a Raspberry Pi starter … [+4441 chars]"
}, ...]
}
I created structures for parse it
import Foundation
struct Article: Codable {
var source: Source
var author: String?
var title: String
var description: String
var url: URL
var urlToImage: URL?
var content: String
enum codingKeys: String, CodingKey {
case source
case author
case title
case description
case url
case urlToImage
case content
}
}
struct Source: Codable {
var name: String
}
struct Articles: Codable {
var articles: [Article]
}
And I created class network service class
class NetworkService {
// MARK: - Methods
func parseJSON(from url: URL) {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
let decoder = JSONDecoder()
let articleData = try decoder.decode(Articles.self, from: data!)
print(articleData.articles.description)
} catch {
print(error)
}
}
task.resume()
}
}
In console I have this:
keyNotFound(CodingKeys(stringValue: "articles", intValue: nil),
Swift.DecodingError.Context(codingPath: [], debugDescription: "No
value associated with key CodingKeys(stringValue: \"articles\",
intValue: nil) (\"articles\").", underlyingError: nil))
Your error is telling you that the JSON was valid, but that it was unable to find any articles key in the response.
I’d suggest including status and message in your definition of Articles and make articles property an optional. The status might not be "ok". Regardless, articles is obviously absent.
struct Articles: Codable {
let articles: [Article]?
let status: String
let message: String?
let code: String?
}
For example, if you don’t supply a valid key, you’ll get a response like
{
"status": "error",
"code": "apiKeyInvalid",
"message": "Your API key is invalid or incorrect. Check your key, or go to https://newsapi.org to create a free API key."
}
You may want to handle the case where articles may be absent if there is an error, by making it optional.
Unrelated, but the forced unwrapping operator for data is dangerous. If you have some network error, your app will crash. I’d suggest unwrapping it, e.g.:
func parseJSON(from url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
do {
let articleData = try JSONDecoder().decode(Articles.self, from: data)
guard let articles = articleData.articles else {
print("No articles", articleData.status, articleData.message ?? "No message")
return
}
for article in articles {
print(article.description)
}
} catch {
print(error)
}
}
task.resume()
}

Swift JSON values

I am new to iOS development and wanting some help here.
I have a JSON output from a webservice and I want to display the details in a custom table view cell. Actually, I am following a tutorial here: https://www.youtube.com/watch?v=ea6_a_zbQrY
In that tutorial, the JSON output is as follows:-
{
"actors": [
{
"name": "Brad Pitt",
"description": "William Bradley 'Brad' Pitt is an American actor and film producer. He has received a Golden Globe Award, a Screen Actors Guild Award, and three Academy Award nominations in acting categories",
"dob": "December 18, 1963",
"country": "United States",
"height": "1.80 m",
"spouse": "Jennifer Aniston",
"children": "Shiloh Nouvel Jolie-Pitt, Maddox Chivan Jolie-Pitt",
"image": "http://microblogging.wingnity.com/JSONParsingTutorial/brad.jpg"
},
{
"name": "Tom Cruise",
"description": "Tom Cruise, is an American film actor and producer. He has been nominated for three Academy Awards and has won three Golden Globe Awards. He started his career at age 19 in the 1981 film Endless Love.",
"dob": "July 3, 1962",
"country": "United States",
"height": "1.70 m",
"spouse": "Katie Holmes",
"children": "Suri Cruise, Isabella Jane Cruise, Connor Cruise",
"image": "http://microblogging.wingnity.com/JSONParsingTutorial/cruise.jpg"
},
{
"name": "Johnny Depp",
"description": "John Christopher 'Johnny' Depp II is an American actor, film producer, and musician. He has won the Golden Globe Award and Screen Actors Guild award for Best Actor.",
"dob": "June 9, 1963",
"country": "United States",
"height": "1.78 m",
"spouse": "Lori Anne Allison",
"children": "Lily-Rose Melody Depp, John 'Jack' Christopher Depp III",
"image": "http://microblogging.wingnity.com/JSONParsingTutorial/johnny.jpg"
},
My own JSON output are as follows:
[{"ID":"5662","Subject":"EXAM [JUNE 17 SEMESTER]","Course":"UNITAR","Lecturer":"EXAM OFFICER","CTime":"9:00AM-5:30PM","Venue":"10.03","TDate":"2017-09-04"},{"ID":"10314","Subject":"FAB","Course":"CAT","Lecturer":"DR CHONG","CTime":"9:00AM-12:00PM","Venue":"THEATRE ROOM 1 [LV 9]","TDate":"2017-09-04"},{"ID":"10317","Subject":"FMA","Course":"CAT","Lecturer":"GS ONG","CTime":"9:00AM-12:00PM","Venue":"9.09","TDate":"2017-09-04"},{"ID":"10318","Subject":"FFA","Course":"CAT","Lecturer":"MARGARET","CTime":"1:00PM-4:00PM","Venue":"THEATRE ROOM 1 [LV 9]","TDate":"2017-09-04"},{"ID":"10319","Subject":"MA1","Course":"CAT","Lecturer":"GS ONG","CTime":"1:00PM-4:00PM","Venue":"9.09","TDate":"2017-09-04"},{"ID":"10320","Subject":"P5","Course":"ACCA","Lecturer":"SPENCER","CTime":"6:15PM-9:45PM","Venue":"THEATRE ROOM 1 [LV 9]","TDate":"2017-09-04"},{"ID":"10324","Subject":"F8","Course":"ACCA","Lecturer":"MIKE KEE","CTime":"6:15PM-9:45PM","Venue":"9.02","TDate":"2017-09-04"},{"ID":"10325","Subject":"F2","Course":"ACCA","Lecturer":"GS ONG","CTime":"6:15PM-9:45PM","Venue":"9.09","TDate":"2017-09-04"},{"ID":"10326","Subject":"F4","Course":"ACCA","Lecturer":"HEMA","CTime":"6:15PM-9:45PM","Venue":"9.13","TDate":"2017-09-04"},{"ID":"11413","Subject":"M4","Course":"TG","Lecturer":"LAI WS","CTime":"7:00PM-10:00PM","Venue":"9.01","TDate":"2017-09-04"}]
Here is the code from the tutorial to parse the JSON values from the tutorial:
func downloadJsonWithURL() {
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
print(jsonObj!.value(forKey: "actors"))
if let actorArray = jsonObj!.value(forKey: "actors") as? NSArray {
for actor in actorArray{
if let actorDict = actor as? NSDictionary {
if let name = actorDict.value(forKey: "name") {
self.nameArray.append(name as! String)
}
if let name = actorDict.value(forKey: "dob") {
self.dobArray.append(name as! String)
}
if let name = actorDict.value(forKey: "image") {
self.imgURLArray.append(name as! String)
}
}
}
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
}).resume()
}
How do I modify this code as I don't have "actors" key in my JSON. Can someone guide me how to change this part?
This is one of the worst codes I've ever seen. Almost everything is wrong or a very bad programming habit.
The biggest mistakes are:
No error handling at all.
The usage of Foundation (NSArray / NSDictionary) rather than native collection types.
The usage of multiple string arrays rather than one custom struct / class as data model.
The forced unwrapping of the values rather than handling the optionals safely.
The usage of valueForKey rather than dedicated objectForKey or key subscription.
First of all create a struct as data model and one array as data source
struct Schedule {
let id, subject, course, lecturer, cTime, venue, tDate : String
}
var schedules = [Schedule]()
Assuming all values won't be changed the struct members are declared as constants (let). You get the memberwise initializer for free.
Reading JSON is very easy. There are only two collection types, array ([]) and dictionary ({}).
This JSON is an array of dictionaries ([{ .. }, { ...}]) . All keys and values are strings. The appropriate (native) Swift type is [[String:String]]. The code parses the JSON and assigns an empty string in case one of the keys does not exist.
func downloadJson(with urlString : String) {
guard let url = URL(string: urlString) else { print("bad URL"); return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let connectionError = error {
print(connectionError)
return
}
do {
if let scheduleArray = try JSONSerialization.jsonObject(with: data!) as? [[String:String]] {
for item in scheduleArray {
self.schedules.append(Schedule(id: item["ID"] ?? "",
subject: item["Subject"] ?? "",
course: item["Course"] ?? "",
lecturer: item["Lecturer"] ?? "",
cTime: item["CTime"] ?? "",
venue: item["Venue"] ?? "",
tDate: item["TDate"] ?? ""))
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} catch {
print(error)
}
}
task.resume()
}
In the table view in cellForRow you can simply write
let schedule = schedules[indexPath.row]
aLabel.text = schedule.id
anotherLabel.text = schedule.subject
...
First ignore the JSON contents, and instead, think of it as an array of objects called as course.
[
{
"ID": "",
"Subject": "",
"Course": "",
"Lecturer": "",
"CTime": "",
"Venue": "",
"TDate": ""
},
...
]
So first you need to parse your JSON as an array. Let's call it as coursesArray.
if let coursesArray = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSArray
{
for course in CoursesArray
{
// course is a json object. So get it into a dictionary so that
// we can access the values in the course.
if let courseDict = course as? NSDictionary
{
// Now we can print the remaining properties of the course
let id = courseDict.value(forKey: "ID")
let subject = courseDict.value(forKey: "Subject")
let courseName = courseDict.value(forKey: "Course")
let lecturer = courseDict.value(forKey: "Lecturer")
let cTime = courseDict.value(forKey: "CTime")
let venue = courseDict.value(forKey: "Venue")
let tDate = courseDict.value(forKey: "TDate")
// Print them, or use them in any way you like now.
}
}
}
That should do about the extraction of the data. To be able to use these, you'll need to append them to other arrays, and reload the table. I'm leaving it to you.
Hope this helps.
Try using following code , take json object as [[String:Any]] and loop through all the present dictionaries in It to get all values you require
//Two arrays to store your data and use it as result
var IDs = [String]()
var Subjects = [String]()
//Your function
func downloadJsonWithURL() {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [[String:Any]] {
print(jsonObj)
//Loop through all the keys present in the dictionaries inside
for key in jsonObj{
//Need to provide required key to check and loop in it
let ID = key["ID"]
let Subject = key["Subject"]
//Append values in Arrays declared above
self.IDs.append(ID as! String)
self.Subjects.append(Subject as! String)
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
}).resume()
}
In the tutorial the author's data format is dictionary of arrays and each array is of type dictionary so, in the code first author converted json data into NSDictionary then accessed array with actor keyword.
But all this steps are not necessary in your case, because your data is directly in an array of dictionaries. So first convert your data as NSArray and then convert each record of it as NSDictionary as shown in below code snippet.
func downloadJsonWithURL() {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [[String:String]] {
print(jsonObj)
for student in jsonObj{
if let studentDict = student as? [String:String] {
if let id = studentDict["ID"] ?? "" {
self.idArray.append(id)
}
if let subject = actorDict["Subject"] ?? "" {
self.subArray.append(subject)
}
}
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
}).resume()
}