Swift - parsing wikidata with urlsession and json decoder not working - ios

I'm trying to build a engine that will take a few pieces of data and parse them through a wikidata query...there are two url session tasks here is my code, but for some reason I can't get the functions to perform the networking to execute...what am I missing?
struct BDayWikiDataManager {
func fetchBDayDataFromWikipedia(numMonth: String, numDay: String) -> (String, String, String) {
var personLabelCode = "no value"
var occupationLabelCode = "no value"
var dob = "no value"
var personName = "no value"
var occupationName = "no value"
let firstURL = "https://query.wikidata.org/sparql?query=SELECT%20distinct%20%3Fperson%20%3FpersonLabel%20%3Fdob%20%3Foccupation%20%3FoccupationLabel%20%3Fawards%20%3FawardsLabel%0AWHERE%0A{%0A%20%20%3Fperson%20wdt%3AP31%20wd%3AQ5%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP27%20wd%3AQ30%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP569%20%3Fdob%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP166%20%3Fawards%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP106%20%3Foccupation.%0A%20%20OPTIONAL%20{%3Fperson%20wdt%3AP166%20%3Fawards}%0A%20%20FILTER(DAY(%3Fdob)%20%3D%20\(numDay)).%0A%20%20FILTER(MONTH(%3Fdob)%20%3D%20\(numMonth)).%0A%20%20FILTER(YEAR(%3Fdob)%20%3E%3D%201940).%0A%20%20FILTER%20(%3Foccupation%20in%20(%20wd%3AQ33999%2C%20wd%3AQ639669%2C%20wd%3AQ2066131%2C%20wd%3AQ947873%2C%20wd%3AQ15265344%20)%20)%0A%20%20SERVICE%20wikibase%3Alabel%20{%20bd%3AserviceParam%20wikibase%3Alanguage%20%22[AUTO_LANGUAGE]%22.%20}%20%0A}%0ALIMIT%2050&format=json"
performRequestFirstURL(with: firstURL) { (data, error) in
if error != nil {
print(error as Any)
}
personLabelCode = data!.personLabel
print(personLabelCode)
occupationLabelCode = data!.occupationLabel
dob = data!.dob
}
print(personLabelCode)
let secondUrl = "https://www.wikidata.org/w/api.php?action=wbgetentities&ids=\(personLabelCode)|\(occupationLabelCode)&format=json&props=labels&languages=en"
let _ = performRequestSecondURL(with: secondUrl, personLabel: personLabelCode, occupationLabel: occupationLabelCode) { (data, error) in
if error != nil {
fatalError("performRequestSecondURL failed, error = \(String(describing: error))")
}
personName = data!.personName
occupationName = data!.occupationName
}
return (personName, occupationName, dob)
}
func performRequestFirstURL(with urlString: String, userCompletionHandler: #escaping (FirstURLData?, Error?) -> Void) {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(FirstURLIncomingData.self, from: data)
print(parsedJSON)
let numberOfBindings = parsedJSON.results.bindings.count
let randomBindingNumber = Int.random(in: 0...(numberOfBindings - 1))
let dob = parsedJSON.results.bindings[randomBindingNumber].dob.value
let personLabel = parsedJSON.results.bindings[randomBindingNumber].personLabel.value
let occupationLabel = parsedJSON.results.bindings[randomBindingNumber].occupationLabel.value
let finalData = FirstURLData(dob: dob, personLabel: personLabel, occupationLabel: occupationLabel)
userCompletionHandler(finalData, nil)
} catch {
print(error)
userCompletionHandler(nil,error)
}
}
})
.resume()
}
}
func performRequestSecondURL(with urlString: String, personLabel: String, occupationLabel: String, userCompletionHandler: #escaping (SecondURLData?, Error?) -> Void) {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(SecondURLIncomingData.self, from: data)
print(parsedJSON)
let name = parsedJSON.entities[personLabel]?.labels.en.value
let occupation = parsedJSON.entities[occupationLabel]?.labels.en.value
let finalData = SecondURLData(personName: name!, occupationName: occupation!)
userCompletionHandler(finalData, nil)
} catch {
print(error)
userCompletionHandler(nil,error)
}
}
})
.resume()
}
}
}
//MARK: - incoming data model - first url
struct FirstURLIncomingData: Codable {
let results: Results
}
struct Results: Codable {
let bindings: [Bindings]
}
struct Bindings: Codable {
let dob: DOB
let personLabel: PersonLabel
let occupationLabel: OccupationLabel
}
struct DOB: Codable {
let value: String
}
struct PersonLabel: Codable {
let value: String
}
struct OccupationLabel: Codable {
let value: String
}
//MARK: - incoming data model - second url
struct SecondURLIncomingData: Decodable {
let entities: [String : Locator]
}
struct Locator: Decodable {
let labels: Labels
}
struct Labels: Decodable {
let en: EN
}
struct EN: Decodable {
let value: String
}
//MARK: - model of data for both urls
struct FirstURLData: Decodable {
let dob: String
let personLabel: String
let occupationLabel: String
}
struct SecondURLData {
let personName: String
let occupationName: String
}
let manager = BDayWikiDataManager()
let data = manager.fetchBDayDataFromWikipedia(numMonth: "8", numDay: "13")
print(data) //prints "no data" for everything, which means the values didnt update
Worth noting: when I go to the url's manually, I get json responses in my browser, so I know the url's are correct...

Related

Parsing JSON File From iTunes API

The code below attempts parse through a json file that contains details about an artist and their music genre and was collected from the iTunes API. I am trying to extract the collectionName and artistName keys from the file with the use of the guard statement but it isn't working. It prints out parsing error.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
musicData()
}
let urlString = "https://itunes.apple.com/search?term=Alex&media=music&entity=album"
func musicData(){
let url = URL(string: urlString)
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let myData = data else {
return
}
guard let rawJSON = try? JSONSerialization.jsonObject(with: myData, options: []),
let json = rawJSON as? [String:Any] else {
print("error serializing JSON")
return
}
guard let musicDictionary = json["collectionName"] as? [String:String] else {
print("Parsing error")
return
}
}
task.resume()
}
let collectionName = musicDictionary["collectionName"]
let artistName = musicDictionary["artistName"]
}
The property you're trying to access is a level deeper. Better approach is to use JSONDecoder.
Model:
struct Response: Codable {
let resultCount: Int
let results: [Result]
}
struct Result: Codable {
let wrapperType: WrapperType
let collectionType: CollectionType
let artistId, collectionId: Int
let amgArtistId: Int?
let artistName, collectionName, collectionCensoredName: String
let artistViewUrl: String?
let collectionViewUrl, artworkUrl60, artworkUrl100: String
let collectionPrice: Double?
let collectionExplicitness: CollectionExplicitness
let trackCount: Int
let copyright: String
let country: Country
let currency: Currency
let releaseDate, primaryGenreName: String
let contentAdvisoryRating: String?
}
enum CollectionExplicitness: String, Codable {
case explicit = "explicit"
case notExplicit = "notExplicit"
}
enum CollectionType: String, Codable {
case album = "Album"
}
enum Country: String, Codable {
case usa = "USA"
}
enum Currency: String, Codable {
case usd = "USD"
}
enum WrapperType: String, Codable {
case collection = "collection"
}
Decoding:
guard let myData = data else {
return
}
do {
let response = try JSONDecoder().decode(Response.self, from: myData)
print(response.results[0].collectionName)
} catch { print(error) }

Load and save struct into UserDefaults

I am trying to save the user in UserDefaults from a struct that fetches the data from an API when a user logs in successfully.
Here is my Webservice class:
import Foundation
import UIKit
struct Resource<T: Codable> {
let url : URL
let httpMethod = HTTPMethod.post
var body : Data? = nil
}
extension Resource {
init(url: URL) {
self.url = url
}
}
enum HTTPMethod : String {
case get = "GET"
case post = "POST"
}
enum NetworkingError: Error {
case domainError
case badResponse
case encodingError
case decodingError
}
class Webservice {
func load<T>(resource: Resource<T>, caller: UIViewController ,completion: #escaping (Result<T, NetworkingError>) -> Void) {
var request = URLRequest(url: resource.url)
request.httpMethod = resource.httpMethod.rawValue
request.httpBody = resource.body
request.addValue("application/JSON", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
return completion(.failure(.domainError))
}
let json = try? JSONSerialization.jsonObject(with: data, options: [])
print(json)
do {
let result = try JSONDecoder().decode(T.self, from: data)
//save to UserDefaults
UserDefaults.standard.set(PropertyListEncoder().encode(T), forKey: "user")\\ here I am getting error that T.type does not conform to Encodable protocol.
completion(.success(result))
}catch {
do {
if let result = try? JSONDecoder().decode(LoginErrorResponse.self, from: data){
print(result.errors.msg)
DispatchQueue.main.async {
let alert = AlertService().alert(message: "\(result.errors.msg[0])")
caller.present(alert, animated: true)
}
completion(.failure(.decodingError))
}
if let result = try? JSONDecoder().decode(SignUpErrorResponse.self, from: data){
print(result.errors.msg)
DispatchQueue.main.async {
let alert = AlertService().alert(message: "\(result.errors.msg)")
caller.present(alert, animated: true)
}
completion(.failure(.decodingError))
}
}
}
}.resume()
}
}
Here is my model class:
import Foundation
struct User: Encodable, Decodable {
let name: String
let email: String
let password: String
let first_name: String
let last_name: String
}
extension User {
static var all : Resource<User> = {
guard let url = URL(string: "http://orderahead.gagzweblab.xyz:3001/login") else {
fatalError("url is incorrect")
}
return Resource<User>(url: url)
}()
static func create(vm : UserViewModel) -> Resource<UserResponseModel?> {
let user = User(vm)
guard let url = URL(string: "http://orderahead.gagzweblab.xyz:3001/register") else {
fatalError("url is incorrect")
}
guard let data = try? JSONEncoder().encode(user) else {
fatalError("error encoding user")
}
var resource = Resource<UserResponseModel?>(url: url)
resource.body = data
return resource
}
}
extension User {
init?(_ vm: UserViewModel) {
let email = vm.email
let password = vm.password
let first_name = vm.first_name
let last_name = vm.last_name
let name = vm.name
self.password = password
self.email = email
self.first_name = first_name
self.last_name = last_name
self.name = name
}
}
And here is my view model:
import Foundation
struct UserViewModel : Codable {
let user : User
}
extension UserViewModel {
var name : String {
return self.user.name
}
var email : String {
return self.user.email
}
var password : String {
self.user.password
}
var first_name: String {
self.user.first_name
}
var last_name: String {
self.user.last_name
}
}
This is how I am calling it:
let login = LoginUser(email: email, password: password)
let vm = UserViewModel(loginUser: login)
Webservice().load(resource: User.create(vm: vm), caller: self) { (result) in
My model and view model conform to Codable as well as my Resource is Codable too.
What is the reason of the error that T.type does not conform to protocol Encodable? How to resolve it?
Is this approach to send and receive data appropriate?
You didn't specify that T should be Encodable for load(resource:... function of class Webservice:
Change this:
class Webservice {
func load<T>(resource: Resource<T>, caller: UIViewController ,completion: #escaping (Result<T, NetworkingError>) -> Void) {
To this:
class Webservice {
func load<T: Encodable>(resource: Resource<T>, caller: UIViewController ,completion: #escaping (Result<T, NetworkingError>) -> Void) {
And also you need to encode value, not generic type here:
UserDefaults.standard.set(PropertyListEncoder().encode(T.self), forKey: "user")
should be
UserDefaults.standard.set(try PropertyListEncoder().encode(result), forKey: "user")
But another question is: Why do you encode from JSON and then encode it to PropertyList? Why not save JSON data in UserDefaults?
may be it will work for you.
extension UserDefaults {
func save<T: Codable>(_ object: T, forKey key: String) {
let encoder = JSONEncoder()
if let encodedObject = try? encoder.encode(object) {
UserDefaults.standard.set(encodedObject, forKey: key)
UserDefaults.standard.synchronize()
}
}
func getObject<T: Codable>(forKey key: String) -> T? {
if let object = UserDefaults.standard.object(forKey: key) as? Data {
let decoder = JSONDecoder()
if let decodedObject = try? decoder.decode(T.self, from: object) {
return decodedObject
}
}
return nil
}}
this is how to store
func setCoableInUser<T: Codable>(_ object:T, key: String)-> Void{
UserDefaults.standard.save(object, forKey: key)}

Getting objects from JSON

My problem:
I use the site API - https://www.themealdb.com/api.php .
I want to get a list of all products. For this purpose, the link is https://www.themealdb.com/api/json/v1/1/categories.php
In my code, I created a structure:
struct Category: Decodable {
var idCategory: Int?
var strCategory: String?
var strCategoryDescription: String?
var strCategoryThumb: String?
}
Then I try to get to the address and get the data. I can convert the incoming data to JSON. It works.
Next, I want to convert the data and write it into an array of structures.
func load(url: String, completion: #escaping (_ objects: [Category])->()) {
guard let url = URL(string: url) else { return }
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: [])
//print("JSONSerialization" + "\(json)")
let object = try JSONDecoder().decode([Category].self, from: data)
print("JSONDecoder" + "\(object)")
completion(object)
} catch {
print(error.localizedDescription)
}
}.resume()
}
But in this line I get an error in the console:
The data couldn’t be read because it isn’t in the correct format.
Probably a mistake in my structure. I can not deal with this problem.
There are two mistakes.
The actual error
Type 'Array' mismatch: Expected to decode Array but found a dictionary instead.
indicates that you are ignoring the root object, the dictionary with key categories
The value for key id is String not Int, note the double quotes in the JSON
Declare all struct members as non-optional constants as the JSON provides all keys the in dictionaries. And please map the horrible dictionary keys to more meaningful member names.
And print all errors and never .localizedDescription in a Decodable catch block.
struct Response: Decodable {
let categories: [Category]
}
struct Category: Decodable {
let id: String
let name: String
let description: String
let thumbnailURL: URL
private enum CodingKeys: String, CodingKey {
case id = "idCategory"
case name = "strCategory"
case description = "strCategoryDescription"
case thumbnailURL = "strCategoryThumb"
}
}
func load(url: String, completion: #escaping ([Category]) -> Void) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let response = try JSONDecoder().decode(Response.self, from: data!)
print("JSONDecoder", response)
completion(response.categories)
} catch {
print(error)
completion([])
}
}.resume()
}
You need two codables
struct MyData: Codable {
var categories: [Category]?
}
And
let object = try JSONDecoder().decode(MyData.self, from: data)
With a wrapper class you can fetch your categories. The following code works fine in Playground:
let json = """
{
"categories": [
{"idCategory": "1"},
{"idCategory": "2"}
]
}
"""
struct CategoryHolder: Codable {
var categories: [Category]
}
struct Category: Codable {
let idCategory: String?
let strCategory: String?
let strCategoryDescription: String?
let strCategoryThumb: String?
}
let jsonData = Data(json.utf8)
let categories = try JSONDecoder().decode(CategoryHolder.self, from: jsonData).categories

struct for nested dictionary API in swift

i'm trying to import JSON data from the v2 of coinmarketcap API. I had it working with v1 as it was an array, however the new version is a dictionary and i cant quite get my struct correct.
The API im using is : https://api.coinmarketcap.com/v2/ticker/?convert=AUD
My struct is set up as below:
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
var id: String
var symbol : String
var name : String
var priceAUD : quoteStruct
}
struct quoteStruct{
let aud : priceStruct
}
struct priceStruct{
let price : String
}
My code for fetching the data is:
var coins = [Coin]()
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
My code for fetching the data i have used the same as previously which worked with v1 of the API, however i don't think i made my struct correctly.
Thanks in advance!
Your response Changed i try to configure it by converting it to array of dictionary you will need to change quotes to be [String:priceStruct]
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id,rank,symbol, name, priceAUD = "quotes"
}
var id: Int
var rank: Int
var symbol : String
var name : String
var priceAUD : [String: priceStruct]
}
struct priceStruct : Decodable{
let price : Double
}
func getCoinData() {
var coins = [Coin]()
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], let resultData = json["data"] as? [String:Any] {
let dataObject = try JSONSerialization.data(withJSONObject: resultData.values.map({$0}) , options: .prettyPrinted)
coins = try JSONDecoder().decode([Coin].self, from: dataObject)
print(coins.count)
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
You response in data parameter should be an array rather than a dictionary. You will not be able to iterate a dictionary over undefined keys. It would be good to get your response of API updated first.
But, If you wish to continue with the existing API response, first you need to convert your response in an array and use your Decodable structs as:
struct Coin: Decodable {
var id: String
var symbol : String
var name : String
var priceAUD : QuoteStruct
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
}
struct QuoteStruct: Decodable {
let aud : PriceStruct
}
struct PriceStruct: Decodable {
let price : String
}
Update your data parsing in API block as:
guard let responseData = data else { return }
do {
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
if let jsonData = json as? [String: Any], let dataObject = jsonData["data"] as? [Int: Any] {
let coinArray = dataObject.map { $0.1 }
if let jsonData = try? JSONSerialization.data(withJSONObject: coinArray, options: .prettyPrinted) {
coins = try JSONDecoder().decode([Coin].self, from: jsonData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
} catch {
print("Error is : \n\(error)")
}

JSONDecoder Not Parsing Data

I am trying to get data from this URL
https://api.opendota.com/api/heroStats
I have made a struct
struct HeroStats : Decodable {
let localized_name: String
let primary_attr: String
let attack_type: String
let legs: Int
let image: String
}
Top of my View Controller
var heros = [HeroStats]()
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://api.opendota.com/api/heroStats")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error.debugDescription)
}
do {
guard let data = data else { return}
self.heros = try JSONDecoder().decode([HeroStats].self, from: data)
DispatchQueue.main.async {
completed()
}
print(self.heros)
} catch {
print("JSON ERROR")
return
}
}.resume()
}
I always return JSON ERROR for some reason, although everything seems to be correct.
Try to read more on Codable/Encodable in Swift
Encoding and Decoding custom types
You may want to improve your code by making Swift names that differs from JSON names
struct HeroStats: Codable {
let name: String
let primaryAttribute: String
let attackType: String // Better to be an enum also
let legs: Int
let image: String?
enum CodingKeys: String, CodingKey {
case name = "localized_name"
case primaryAttribute = "primary_attr"
case attackType = "attack_type"
case legs
case image = "img"
}
}

Resources