Rest API JSON request in Swift 2 - json

I'm trying to make an application using Opendata.
This is my JSON data: http://datatank.stad.gent/4/infrastructuur/publieksanitair
The problem I have is that I don't know how to parse this JSON file.
I'm always getting "Data does not contain a root object."
So it goes wrong in the Service.swift file. I'm sure my request works because when debugging I see data is returned, but I don't know how to handle it.
You can pull my project from: https://github.com/StijnPil/iOSProjectShared/tree/develop
but I've also put the important code below:
Service.swift
import Foundation
class Service
{
enum Error: ErrorType
{
case InvalidJsonData(message: String?)
case MissingJsonProperty(name: String)
case MissingResponseData
case NetworkError(message: String?)
case UnexpectedStatusCode(code: Int)
}
static let sharedService = Service()
private let url: NSURL
private let session: NSURLSession
private init() {
let path = NSBundle.mainBundle().pathForResource("Properties", ofType: "plist")!
let properties = NSDictionary(contentsOfFile: path)!
url = NSURL(string: properties["baseUrl"] as! String)!
session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration())
}
func createFetchTask(completionHandler: Result<[PubliekSanitair]> -> Void) -> NSURLSessionTask {
return session.dataTaskWithURL(url) {
data, response, error in
let completionHandler: Result<[PubliekSanitair]> -> Void = {
result in
dispatch_async(dispatch_get_main_queue()) {
completionHandler(result)
}
}
guard let response = response as? NSHTTPURLResponse else {
completionHandler(.Failure(.NetworkError(message: error?.description)))
return
}
guard response.statusCode == 200 else {
completionHandler(.Failure(.UnexpectedStatusCode(code: response.statusCode)))
return
}
guard let data = data else {
completionHandler(.Failure(.MissingResponseData))
return
}
do {
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [NSDictionary] else {
completionHandler(.Failure(.InvalidJsonData(message: "Data does not contain a root object.")))
return
}
//old code
//let lots = try json.filter { $0["city"]?["name"] as? String == "Gent" }.map { try ParkingLot(json: $0) }
//new code
let lots = try json.map{ try PubliekSanitair(json: $0)}
completionHandler(.Success(lots))
} catch let error as Error {
completionHandler(.Failure(error))
} catch let error as NSError {
completionHandler(.Failure(.InvalidJsonData(message: error.description)))
}
}
}
}
Result.swift
enum Result<T>
{
case Success(T)
case Failure(Service.Error)
}
PubliekSanitair.swift
import Foundation
class PubliekSanitair
{
let type_sanit: String
init(type_sanit: String){
self.type_sanit = type_sanit
}
}
extension PubliekSanitair
{
convenience init(json: NSDictionary) throws {
guard let document = json["Document"] as? NSDictionary else{
throw Service.Error.MissingJsonProperty(name: "Document")
}
guard let folder = document["Folder"] as? NSDictionary else{
throw Service.Error.MissingJsonProperty(name: "Folder")
}
guard let placemark = folder["Placemark"] as? NSDictionary else{
throw Service.Error.MissingJsonProperty(name: "Placemark")
}
guard let extendedData = placemark["ExtendedData"] as? NSDictionary else{
throw Service.Error.MissingJsonProperty(name: "Placemark")
}
guard let schemaData = extendedData["SchemaData"] as? NSDictionary else{
throw Service.Error.MissingJsonProperty(name: "Placemark")
}
guard let simpleData = schemaData["SimpleData"] as? NSDictionary else{
throw Service.Error.MissingJsonProperty(name: "Placemark")
}
guard let type_sanit = simpleData[0]!["#text"] as? String else{
throw Service.Error.MissingJsonProperty(name: "#text in type_sanit")
}
self.init(type_sanit: type_sanit)
}
}

Related

How to get json fields?

I follow a lesson from one course
And I need to get json, but i want get another json than in a lesson.
So this is my json:
https://api.scryfall.com/cards/search?q=half
And code:
struct Card {
var cardId: String
var name: String
var imageUrl: String
var text: String
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String,
let cardId = dict["cardId"] as? String,
let imageUrl = dict["imageUrl"] as? String,
let text = dict["text"] as? String else { return nil }
self.cardId = cardId
self.name = name
self.imageUrl = imageUrl
self.text = text
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping(GetCardResponse) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
do {
print ("ok1")
let response = try GetCardResponse(json: json)
print ("ok2")
completion(response)
} catch {
print(error)
}
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
print(json)
} catch {
print(error)
}
}.resume()
}
}
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let array = json as? [[String: AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in array {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
Problem in struct GetCardResponse and [[String: AnyObject]] because I dont know how to parse this type of json. I tried to change them in the likeness of json. But I dont really understand how it works and in which part of code i need to put json["data"] or something like this... Help pls. I just want get json fields tcgplayer_id, name, art_crop
As of your code, you can parse the required details as:
struct Card {
var cardId: String = ""
var name: String = ""
var imageUrl: String = ""
var text: String = ""
init(dict: [String: Any]) {
if let obj = dict["name"] {
self.name = "\(obj)"
}
if let obj = dict["tcgplayer_id"] {
self.cardId = "\(obj)"
}
if let obj = dict["image_uris"] as? [String:Any], let url = obj["art_crop"] {
self.imageUrl = "\(url)"
}
if let obj = dict["oracle_text"] {
self.text = "\(obj)"
}
}
static func models(array: [[String:Any]]) -> [Card] {
return array.map { Card(dict: $0) }
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping([Card]?) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
print ("ok1")
if let jData = json as? [String:Any], let data = jData["data"] as? [[String:Any]] {
let response = Card.models(array: data)
completion(response)
}
completion(nil)
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
} catch {
print(error)
}
}.resume()
}
}
CardNetworkService.getCards(url: "https://api.scryfall.com/cards/search?q=half") { (res) in
print(res ?? [])
}
Just paste this code in playground and it'll work.
Happy Coding :)
You are wrong get entry of data field.
First you need get data field in json. And parse to deeper.
Try use the code.
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let jsonObject = json as? [String: Any], let data = jsonObject["data"] as? [[String:AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in data {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
UPDATE:
init function in Card has something wrong. In your json cardId is not found
Card class maybe like this because cardId, imageUrl, text maybe not found. It is optional
struct Card {
var cardId: String?
var name: String
var imageUrl: String?
var text: String?
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String else { return nil }
self.cardId = dict["cardId"] as? String
self.name = name
self.imageUrl = dict["imageUrl"] as? String
self.text = dict["text"] as? String
}
}
Try using Codable to parse the JSON data like so,
Create the models like,
struct Root: Decodable {
let cards: [Card]
enum CodingKeys: String, CodingKey {
case cards = "data"
}
}
struct Card: Decodable {
let tcgplayerId: Int
let name: String
let artCrop: String
}
Now parse your JSON data using,
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
}
You can access the properties in cards of response like so,
response.cards.first?.tcgplayerId

Save JSON response as JSON file

I want to save my JSON response to a JSON file in document or any other directory.
Earlier I was trying to save the response in coreData but that was a heavy and slow task.
//API Manager function
func loadEmployees(urlString: String, completion: #escaping ((Any?,Error?) -> ())){
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = RequestMethod.get.rawValue
let session = URLSession.shared
let sessionTask = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let result = try? JSONDecoder().decode([EmployeeDetails].self, from: data!)
completion(result, nil)
}
else {
completion(nil, ServiceError.customError("Please check your internet connection"))
}
}
sessionTask.resume()
}
//I am calling it in my View Controller
NetworkManager.sharedInstance.loadEmployees(urlString: EMPLOYEEBASEURL, completion: { (data, responseError) in
if let error = responseError {
self.showToast(controller: self, message: error.localizedDescription, seconds: 1.6)
}else{
if data != nil {
DispatchQueue.global().async {
self.employeeListArray = data as! [EmployeeDetails]
self.filteredEmployeeArray = self.employeeListArray
DispatchQueue.main.async {
self.loader.isHidden = true
self.employeeTableView.reloadData()
}
}
}
}
})
//My model
struct EmployeeDetails: Decodable {
let id: String?
let name: String?
let salary: String?
let age: String?
let profileImage: String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "employee_name"
case salary = "employee_salary"
case age = "employee_age"
case profileImage = "profile_image"
}
}
Now Instead of parsing it directly I want to save the response in a json file and parse from the file.
I can install any pods if required, my Project is in Swift 5.0 so newer methods are also acceptable.
To save:-
func saveJsonFile(_ name:String, data:Data) {
// Get the url of File in document directory
guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileUrl = documentDirectoryUrl.appendingPathComponent(name + ".json")
// Transform array into data and save it into file
do {
//let data = try JSONSerialization.data(withJSONObject: list, options: [])
try data.write(to: fileUrl, options: .completeFileProtection)
} catch {
print(error)
}
}
Retrive:-
func retrieveFromJsonFile(_ name:String) -> [JSONObject]? {
// Get the url of File in document directory
guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil}
let fileUrl = documentsDirectoryUrl.appendingPathComponent(name + ".json")
// Check for file in file manager.
guard (FileManager.default.fileExists(atPath: fileUrl.path))else {return nil}
// Read data from .json file and transform data into an array
do {
let data = try Data(contentsOf: fileUrl, options: [])
guard let list = try JSONSerialization.jsonObject(with: data, options: []) as? [JSONObject] else { return nil}
//print(list)
return list
} catch {
print(error)
return nil
}
}
Delete json file:-
func removeFile(with name: String){
// Path for the file.
guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return}
let fileUrl = documentsDirectoryUrl.appendingPathComponent(name + ".json")
if (FileManager.default.fileExists(atPath: fileUrl.absoluteString)){
do{
try FileManager.default.removeItem(at: fileUrl)
}catch{
print(error.localizedDescription)
}
}
}
where JSONObject:- [String: Any]

setValuesForKeys resulting to this error: setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key type

This is the first time that I will be attempting to use the method setValuesForKeys and for some reason, I keep stumbling upon the error as stated below:
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key type.
I've gone through several related questions here but to no avail. Here's my code below:
class ResumeCategory: NSObject {
var name: String?
var resumes: [Resume]?
static func getJSON() {
let urlString = "https://api.letsbuildthatapp.com/appstore/featured"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
DispatchQueue.main.async {
if let error = error {
print("Failed to get data from URL: ", error)
return
}
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
print(json["categories"]!)
var resumeCategories = [ResumeCategory]()
for dict in json["categories"] as! [[String: Any]] {
let resumeCategory = ResumeCategory()
resumeCategory.setValuesForKeys(dict)
resumeCategories.append(resumeCategory)
}
} catch let error {
print("Failed to parse server response: ", error)
}
}
}.resume()
}

Struct Init with JSON and flatMap

I'm having a problem with the following code. I'm downloading a list of actors in JSON and I want to populate Struct Actor with the received data. Everything works great until I try to flatMap on the received data and try to initialize the struct Actor. When I try to compile the code i get the error: Cannot assign value of type '()' to type [Actor]. The error corresponds to a line in viewDidLoad actorsList = downloadActors() Would anybody have any recommendation who to solve this?
import UIKit
func downloadActors() {
var request = URLRequest(url: URL(string: "url...")!)
request.httpMethod = "POST"
let postString = "actorGroup=\("Superhero")"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
guard let data = data, error == nil else {
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("error : statusCode should be 200 but is \(httpStatus.statusCode)")
print("response = \(response)")
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: AnyObject]
guard let actorsJSON = json?["response"] as? [[String : AnyObject]] else {
return
}
} catch {
print("catch error")
}
}
}
}
task.resume()
}
func loadActors() -> [Actor] {
if let actors = actorsJSON as? [[String : AnyObject]] {
return actors.flatMap(Actor.init)
}
}
let actorsArray = loadActors()
class MasterViewController: UITableViewController {
var actorsList = [Actor]()
var detailViewController: DetailViewController? = nil
var objects = [Any]()
override func viewDidLoad() {
super.viewDidLoad()
actorsList = downloadActors()
print(actorsList)
Struct Actors is as follows:
struct Job {
let actorGroup: String
let actorName: String
}
extension Actor: JSONDecodable {
init?(JSON: [String : AnyObject]) {
guard let actorGroup = JSON["actorGroup"] as? String, let actorName = JSON["actorName"] as? String else {
return nil
}
self. actorGroup = actorGroup
self. actorName = actorName
}
}
let listActors = actorsJSON as? [[String : AnyObject]] {
Should be:
if let listActors = actorsJSON as? [[String : AnyObject]] {
Edit: For more info I'd like to add Vadian's comment:
Very confusing code. What does the function in the middle of the do block? Why do you type-check actorsJSON twice? The computed property is let listActors... which should be probably an optional binding (if let ... ). Further .mutableContainers is completely nonsense in Swift. And finally a JSON dictionary is [String:Any] in Swift 3.

Error parsing JSON in Swift2

Hello I am having an error when parsing some JSON in Swift, my error is:
'Invalid conversion from throwing function of type '(_, _, _) throws -> Void' to non-throwing function type '(NSData?, NSURLResponse?, NSError?) -> Void'
I think this is something to do with catching an error somewhere but I cannot figure out where this would go could anyone help me please? Here is my source code:
import UIKit
import CoreData
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
var appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context: NSManagedObjectContext = appDel.managedObjectContext
let url = NSURL(string: "https://www.googleapis.com/blogger/v3/blogs/10861780/posts?key=AIzaSyBwmI4AzMnBmr7oSVeL0EHdzMjXV1aATnQ")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error)
} else {
//print(NSString(data: data!, encoding: NSUTF8StringEncoding))
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(rawValue: 0)) as! NSDictionary
if jsonResult.count > 0 {
if let items = jsonResult["items"] as? NSArray {
for items in items {
print(items)
if let title = items["title"] as? String {
if let content = items["content"] as? String {
var newPost: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Posts", inManagedObjectContext: context)
newPost.setValue(title, forKey: "title")
newPost.setValue(content, forKey: "content")
do {
try context.save()
}
}
}
}
}
}
} catch let error as NSError {
print(error)
}
var request = NSFetchRequest(entityName: "Posts")
request.returnsObjectsAsFaults = false
var results = try context.executeFetchRequest(request)
self.tableView.reloadData()
}
})
task.resume()
}
Thanks!
It looks like you haven't taken care of all the throwing functions using do-try-catch method.
According to the Swift 3 Documentation at https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html
In your case, you seem to have forgotten to take care of the error thrown at var- results. Also, you haven't handled the throwing function session.dataTaskWithURL with a do-try-catch method.
You should not be getting this error if you modify your code as following:
override func viewDidLoad() {
super.viewDidLoad()
var appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context: NSManagedObjectContext = appDel.managedObjectContext
let url = NSURL(string: "https://www.googleapis.com/blogger/v3/blogs/10861780/posts?key=AIzaSyBwmI4AzMnBmr7oSVeL0EHdzMjXV1aATnQ")
let session = NSURLSession.sharedSession()
do {
let task = try session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error)
} else {
//print(NSString(data: data!, encoding: NSUTF8StringEncoding))
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(rawValue: 0)) as! NSDictionary
if jsonResult.count > 0 {
if let items = jsonResult["items"] as? NSArray {
for items in items {
print(items)
if let title = items["title"] as? String {
if let content = items["content"] as? String {
var newPost: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Posts", inManagedObjectContext: context)
newPost.setValue(title, forKey: "title")
newPost.setValue(content, forKey: "content")
do {
try context.save()
} catch let error1 as NSError { // this is for context.save
print(error1)
}
}
}
}
}
}
}
var request = NSFetchRequest(entityName: "Posts")
request.returnsObjectsAsFaults = false
do{
var results = try context.executeFetchRequest(request)
} catch let error2 as NSError { // this is for context.executeFetchRequest
print(error2)
}
self.tableView.reloadData()
}
})
task.resume()
} catch let error3 as NSError { // this is for session.dataTaskWithURL
print(error3)
}
}
Hope this helps ! Cheers !