Swift parsing json, and show to the user issue - json

I am parsing a JSON on my own, after seeing a couple of tutorials, I try to adapt an OMBD API, but Xcode is throwing me this error Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee7ca9f68), how do I debug this, below is what I've done so far.
import UIKit
import SnapKit
class ViewController: UIViewController, MovieManagerDelegate {
lazy var titleLabel: UILabel = {
UILabel()
}()
var movieManager = MovieManager()
override func viewDidLoad() {
super.viewDidLoad()
movieManager.delegate = self
movieManager.performMovieRequest(urlRequest: movieManager.movieUrl)
viewHierarchy()
constraitsMaker()
additionalComponents()
}
func viewHierarchy() {
view.addSubview(titleLabel)
}
func constraitsMaker() {
titleLabel.snp.makeConstraints { (maker) in
maker.center.leading.trailing.equalToSuperview()
}
}
func additionalComponents() {
titleLabel.textColor = .black
}
func didUpdateTitle(movie: MovieModel) {
DispatchQueue.main.async {
self.titleLabel.text = movie.movieTitle
}
}
}
import Foundation
protocol MovieManagerDelegate {
func didUpdateTitle(movie: MovieModel)
}
struct MovieManager {
let viewController = ViewController()
let movieUrl = "https://www.omdbapi.com/?i=tt3896198&apikey=b6531970"
let posterUrl = "https://m.media-amazon.com/images/M/MV5BNjM0NTc0NzItM2FlYS00YzEwLWE0YmUtNTA2ZWIzODc2OTgxXkEyXkFqcGdeQXVyNTgwNzIyNzg#._V1_SX300.jpg"
var delegate: MovieManagerDelegate?
func performMovieRequest(urlRequest: String) {
if let url = URL(string: movieUrl) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
let movie = self.parseJSON(movieData: safeData)
self.delegate?.didUpdateTitle(movie: movie!)
}
}
task.resume()
}
}
//parse json function
func parseJSON(movieData: Data) -> MovieModel? {// with data as a parameter
let decoder = JSONDecoder()
//do try catch to handle errors of decoded json
do {
let decodedData = try decoder.decode(MovieData.self, from: movieData)//call the data for decode json. movie data comes from the parameter of the function, that is subclassed as data object
let title = decodedData.Title
let movie = MovieModel(movieTitle: title)
return movie
} catch {
print(error)
return nil
}
}
import Foundation
struct MovieData: Codable {
let Title: String
let Year: String
let Rated: String
let Writer: String
let Released: String
let Runtime: String
let Genre: String
let Director: String
let Actors: String
let Plot: String
let Language: String
let Country: String
let Awards: String
let Poster: URL
let Ratings: [Ratings]
let Metascore: String
let `Type`: String
let DVD: String
let BoxOffice: String
let Production: String
}
struct Ratings: Codable {
let Source: String
let Value: String
}
import Foundation
struct MovieModel {
let movieTitle: String
}

MovieManager makes an asynchronous call when downloading data meaning the code after the call to performMovieRequest is executed before the data is downloaded and the label has been initialised. It should work fine to move the calls in viewDidLoad to the delegate method
func didUpdateTitle(movie: MovieModel) {
viewHierarchy()
constraitsMaker()
additionalComponents()
DispatchQueue.main.async {
self.titleLabel.text = movie.movieTitle
}
}
If you are calling the delegate method after that as well you might want to have a boolean property to verify the 3 methods from viewDidLoad doesn't get called again

Related

Swift JSON with dynamic Keys

I am trying to parse JSON using Swift, which has dynamic keys. Tried several ways but still did not find the solution. Could you please help me ?
I am trying to parse NativeName, which is dynamic based on which language country name is present.
API: https://restcountries.com/v3.1/all
struct Objects: Codable {
let name: Name
let cca2 : String
let flag: String
}
struct Name: Codable {
let common, official: String
let nativeName: NativeName
}
struct NativeName: Codable {
var deu : Deu
}
struct Deu: Codable {
let official, common: String?
}
and here is JSON Model:
class ParsingService {
static let shared = ParsingService()
func fetchData() {
guard let url = URL(string: "https://restcountries.com/v3.1/all") else {
print("DEBUG: URL is nill")
return}
let session = URLSession.shared
let task = session.dataTask(with: url) { data, _, error in
guard let retrievedData = data, error == nil else {
print("DEBUG: Data is not available")
return}
print("DEBUG: Data is available \(retrievedData)")
guard let decodedData = self.JSONParsing(inputData: retrievedData) else {
print("DEBUG: Missing data")
return}
print("DEBUG: Data is there")
print("DEBUG: \(decodedData[0].cca2)")
print("DEBUG: \(decodedData[0].flag)")
print("DEBUG: \(decodedData[0].name.nativeName.deu.official)")
DispatchQueue.main.async {
print(decodedData.currencies)
}
}
task.resume()
}
func JSONParsing(inputData: Data)-> [Objects]? {
let decoder = JSONDecoder()
do {
let data = try? decoder.decode([Objects].self, from: inputData)
return data
} catch {
print("DEBUG: Cannot get data")
return nil
}
}
}
you could try this approach:
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
fetchData() { results in
print("---> results: \(results.count) \n")
for i in 0..<3 {
print("---> results[\(i)]: \(results[i].name.nativeName)")
}
}
}
}
// todo deal with errors
func fetchData(completion: #escaping ([Objects]) -> Void) {
let url = URL(string: "https://restcountries.com/v3.1/all")
guard let url = url else { completion([]); return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { completion([]); return }
do {
let results = try JSONDecoder().decode([Objects].self, from: data)
completion(results)
}
catch {
print("Error: \(error)")
completion([])
}
}.resume()
}
}
struct Objects: Codable {
let name: Name
let cca2 : String
let flag: String
}
struct Deu: Codable {
let official, common: String?
}
struct Name: Codable {
let common, official: String
let nativeName: NativeName? // <-- here
}
// -- here
struct NativeName: Codable {
var lang: [String: Deu]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
lang = try container.decode([String: Deu].self)
}
func encode(to encoder: Encoder) throws {
// todo
}
}
Note, you could also use a Tuple, such as var lang: (key: String, value: Deu)

How to parse JSON using Codable in Swift?

I am able to parse JSON using JSONSerialization, but unable to parse with Codable.
the json look like this:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere#april.biz",
}
Please help me with the code.
able to parse using JSONSerialization: data coming
Unable to parse JSON with Codable: data not coming
struct jsonDataModel: Codable{
var name: String
var userName: String
var email: String
init(name: String, username: String, email: String){
self.name = name
self.userName = username
self.email = email
}
}
class WebviewViewController: UIViewController, WKNavigationDelegate {
#IBOutlet weak var testWebView: WKWebView!
//var itemsArray = [jsonDataModel]()
override func viewDidLoad() {
super.viewDidLoad()
serviceCall()
}
func serviceCall()
{
let jsonString = "https://jsonplaceholder.typicode.com/users"
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(jsonDataModel.self, from: jsonData)
print("all data \(user)")
print("Hello \(user.name), \(user.userName), \(user.email) ")
} catch {
print("Unexpected error: \(error).")
}
}
}
Please help me to parse json with codable.
Try this example.
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do{
let jsonDataModels = try JSONDecoder().decode([JSONDataModel].self, from: data)
print(String(data: data, encoding: .utf8)!)
print("jsonDataModels: \(jsonDataModels)")
}catch{}
}
task.resume()
}
}
struct JSONDataModel: Codable {
let id: Int
let name, username, email: String
let address: Address
let phone, website: String
let company: Company
}
struct Address: Codable {
let street, suite, city, zipcode: String
let geo: Geo
}
struct Geo: Codable {
let lat, lng: String
}
struct Company: Codable {
let name, catchPhrase, bs: String
}
First of all, if you're using a URL, then to get data you need to use a networking api. URLSession is the iOS provided api to perform network operations like download/upload.
So, just using Codable doesn't make any sense. You need to first have the data in order to parse it with Codable.
Here is the model,
struct Model: Codable {
let id: Int
let name, username, email: String
}
And you can use it in your controller's viewDidLoad() method,
if let url = URL(string: "https://jsonplaceholder.typicode.com/users") {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
do {
let response = try JSONDecoder().decode([Model].self, from: data)
print(response.map({ $0.name })) //prints all the names
} catch {
print(error)
}
}
}.resume()
}

How do I fix error: "Expected to decode Dictionary<String, Any> but found an array instead" in this code?

import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//API Key: 5ca10b2d20a545099a108a3aeceb329c
//url: https://newsapi.org/v2/top-headlines?country=us&apiKey=5ca10b2d20a545099a108a3aeceb329c
// model
struct Source: Decodable {
var id: String
var name: String
}
struct Articles: Decodable {
var source: Source
var author: String
var title: String
var description: String
var url: String
var urlToImage: String
var publishedAt: String
var content: String
}
struct JSONDescription: Decodable {
var status: String
var totalResults: Int
var articles: Articles
}
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&apiKey=5ca10b2d20a545099a108a3aeceb329c") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
//let dataAsString = String(data: data, encoding: .utf8)
// print(dataAsString)
do {
let jsonDescription = try JSONDecoder().decode(JSONDescription.self, from: data)
print(jsonDescription.totalResults)
}
catch let jsonError {
print("Json Error:", jsonError)
}
}.resume()
}
}
What I expected to see was the JSON data returned here: https://newsapi.org/v2/top-headlines?country=us&apiKey=5ca10b2d20a545099a108a3aeceb329c
You can put it into this formatter to make is readable: https://jsonformatter.curiousconcept.com
I thought I did everything correctly. Have I built my model wrong? I'm not sure how to fix this error.
So, with the help of the error returned, and looking at the data, it seems that "articles" is an array.
Here's what I'd try:
Rename your Articles struct to Article
Change JSONDescription's articles property from Articles to [Article]
I didn't notice any other errors in the data mapping, but hopefully this gets you closer.

Need help by parsing with JSON

I am a total beginner with programming and Swift. One of the features my app should have is a label of the current temperature in New York City. The problem is I don't know if I am on the right path with my code. I tried things from many videos and articles but nothing seems to work. I am sure the answer is very easy. Thanks for any help!
I use the darksky api. This is my current code.
import UIKit
struct MyGitHub: Codable {
let temperature: Int
private enum CodingKeys: String, CodingKey {
case temperature
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard let gitUrl = URL(string: "here is my darksky api") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(MyGitHub.self, from: data)
print (gitData.temperature)
} catch let err {
print ("Err", err)
}
} .resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You need an umbrella struct for the root object and the value for key temperature is Double, not Int
struct Root: Decodable {
let currently : MyGitHub
}
struct MyGitHub: Decodable {
let temperature: Double
}
...
let gitData = try decoder.decode(Root.self, from: data)
print(gitData.currently.temperature)
You can try this
Alamofire.request(urlStr, method: .get, parameters:nil, encoding: JSONEncoding.default).responseJSON { response in
if let json = response.result.value as? [String:Any] {
if let main = json["currently"] as? [String:Any] {
if let temp = main["temperature"] as? NSNumber
{
// set lbl here
print(temp)
}
}
}
}
OR
struct Currently : Codable {
var currently:InnerItem
}
struct InnerItem : Codable {
var temperature:Double
}
with
guard let gitUrl = URL(string:urlStr) else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(Currently.self, from: data)
print ("sdhjsdjhjhdshjshjsjhddhsj" , gitData.currently.temperature)
} catch let err {
print ("Err", err)
}
} .resume()

Swift - Parsing Json - Get values

i started working with the SwiftyJson Class in order to parse json from the web.
I started with this RestApiManager:
typealias ServiceResponse = (JSON, NSError?) -> Void
class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()
let baseUrl = "http://api.randomuser.me/"
func getRandomUser(onCompletion: (JSON) -> Void) {
makeHttpGetRequest(baseUrl, onCompletion: { json, err -> Void in
onCompletion(json)
})
}
func makeHttpGetRequest(path: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error in
let json: JSON = JSON(data)
onCompletion(json, error)
})
task.resume()
}
}
In my controller i called this method:
RestApiManager.sharedInstance.getRandomUser { json -> Void in
let results = json["results"]
for (index: String, subJson: JSON) in results {
println(subJson["user"]["gender"].stringValue)
}
}
But it just prints
(lldb)
and some useless information..
Anybody could help me with this problem?
I learned with this tutorial
https://www.youtube.com/watch?v=YX9vK11oX-E
and this git link:
https://github.com/SwiftyJSON/SwiftyJSON