how to fetch data from a dictionary using swift 4 and struct? - json

struct family: Decodable {
let userId: [String:Int]
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "http://supinfo.steve-colinet.fr/supfamily?action=login&username=admin&password=admin"
let urlobj = URL(string: url)
URLSession.shared.dataTask(with: urlobj!){(data, response, error) in
do{
let member = try JSONDecoder().decode(family.self, from: data!)
print(member)
}catch{
print(error)
}
}.resume()
}
}
Error:
keyNotFound(CodingKeys(stringValue: "userId", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"userId\", intValue: nil) (\"userId\").", underlyingError: nil))

The issue is that the userId key is nested in the JSON response. You need to decode the response from its root.
struct Family: Decodable {
let id: Int
let name: String
}
struct User: Codable {
let userId: Int
let lasName: String
let firstName: String
}
struct RootResponse: Codable {
let family: Family
let user: User
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "http://supinfo.steve-colinet.fr/supfamily?action=login&username=admin&password=admin"
let urlobj = URL(string: url)
URLSession.shared.dataTask(with: urlobj!){(data, response, error) in
do{
let rootResponse = try JSONDecoder().decode(RootResponse.self, from: data!)
print(rootResponse)
}catch{
print(error)
}
}.resume()
}
}

Related

"Expected to decode Array<Any> but found a dictionary instead." How can i solve this

i'm trying to do with API Call. I got error every time trying to do API Call.
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
this is what i see when simulate my code in console.
Here is json format i try to call in my app. Click
My Model
struct Article: Codable {
let author: String
let title, articleDescription: String
let url: String
let urlToImage: String
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
and This is my API Call function.
import UIKit
class ViewController: UIViewController {
var article = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
jsonParse {
print("success")
}
view.backgroundColor = .red
}
func jsonParse(completed: #escaping () -> ()) {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=tr&apiKey=1ea9c2d2fbe74278883a8dc0c9eb912f")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if error != nil {
print(error?.localizedDescription as Any)
}else {
do {
let result = try JSONDecoder().decode([Article].self, from: data!)
DispatchQueue.main.async {
print(data as Any)
print("success")
self.jsonParse {
print("success")
}
}
}catch {
print(error.localizedDescription)
}
}
}
task.resume()
}
}
Can you help me about my problem, thank you.
This is a very common mistake: You ignore the root object, a dictionary. With Decodable it's mandatory to decode the JSON from the top.
Add this struct
struct Response: Decodable {
let status: String
let totalResults: Int
let articles: [Article]
}
and decode
let result = try JSONDecoder().decode(Response.self, from: data!)
And never print(error.localizedDescription) in a Codable catch block. Always write this
} catch {
print(error)
}

Swift parsing JSON into Table View doesnt work

Hi I have problems to read the Json Data into the TableView.
Can anybody help me, its my first time working with it.
I know I am doing something wrong, but I cant finde the Solution for my JSON File cause in the Internet they use simple ones..
Here is my JSON:
{
"data":[
{
"title": "Brot",
"desc":[
{
"Name": "Roggenschrot- und Roggenvollkornbrot",
"Menge": "Gramm",
"Kalorie": "2",
"Energiedichte": "Gelb"
},
{
"Name": "Weizenschrot- und Weizenvollkornbrot",
"Menge": "Gramm",
"Kalorie": "2",
"Energiedichte": "Gelb"
},
{
"Name": "Weizenschrot- und Weizenvollkornbrot",
"Menge": "Gramm",
"Kalorie": "2",
"Energiedichte": "Gelb"
},
]
}
]
}
Here is my tableView
import UIKit
class EseenTagebuchTableViewController: UITableViewController {
var result: Result?
var resultItem: ResultItem?
override func viewDidLoad() {
super.viewDidLoad()
parseJSON()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return result?.data.count ?? 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return result?.data[section].title
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if let result = result {
return result.data.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let text = resultItem?.desc?[indexPath.section].Name?[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = text
return cell
}
private func parseJSON() {
guard let path = Bundle.main.path(forResource: "Ernahrungstagebuch", ofType: "json") else {
return
}
let url = URL(fileURLWithPath: path)
do {
let jsonData = try Data(contentsOf: url)
result = try JSONDecoder().decode(Result.self, from: jsonData)
return
}
catch {
print("Error: \(error)")
}
}
}
And here are my Model 1 and after my Model 2
import Foundation
struct Result: Codable {
let data: [ResultItem]
}
struct ResultItem: Codable {
let title: String
let desc: [descItems]?
}
Model 2
import Foundation
struct descItems: Codable {
let Name: String?
let Menge: String?
let Kalorie: Int?
let Energiedichte: String?
}
What am I doing wrong?
First your json data in your file is not correct, there is an extra "," comma after
the last "Energiedichte": "Gelb" , remove that from your file.
Second your model is not correct, you should have:
struct Result: Codable {
let data: [ResultItem]
}
struct ResultItem: Codable {
let title: String
let desc: [DescItems]?
}
struct DescItems: Codable {
let Name: String?
let Menge: String?
let Kalorie: String? // <-- here not Int?
let Energiedichte: String?
}
Using your code parseJSON() I was able to parse the data without errors.
EDIT-1: if you really want kalories to be Int, try:
struct DescItems: Codable {
let name: String?
let menge: String?
let kalorie: Int? // <-- here
let energiedichte: String?
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
menge = try container.decode(String.self, forKey: .menge)
energiedichte = try container.decode(String.self, forKey: .energiedichte)
let calorie = try container.decode(String.self, forKey: .kalorie) // <-- here
kalorie = Int(calorie) // <-- here
}
enum CodingKeys: String, CodingKey {
case name = "Name"
case menge = "Menge"
case kalorie = "Kalorie"
case energiedichte = "Energiedichte"
}
}
Note the change of case, as per common practice.
you can also use json parse below code:
func parseJSON() {
let path = Bundle.main.path(forResource: "Ernahrungstagebuch", ofType: "json")
let jsonData = try? NSData(contentsOfFile: path!, options: NSData.ReadingOptions.mappedIfSafe)
let jsonResult: NSDictionary = try! (JSONSerialization.jsonObject(with: jsonData! as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary)!
if let data = jsonResult["data"] as? [[String:Any]] {
self.arrPlan = data.map{Day(JSON: $0)!}
// print(arrPlan)
}
}
Try to this code for get data array object and after reload tableview Data.
You can use this extension to parse json.
import Foundation
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = dateDecodingStrategy
decoder.keyDecodingStrategy = keyDecodingStrategy
do {
return try decoder.decode(T.self, from: data)
} catch DecodingError.keyNotFound(let key, let context) {
fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
} catch DecodingError.typeMismatch(_, let context) {
fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
} catch DecodingError.dataCorrupted(_) {
fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
} catch {
fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
}
}
}
And use it like this:
let incomingData = Bundle.main.decode([Your_Model].self, from: "Your_Json_File_name.json")

No value associated with key CodingKeys error

I'm trying to retrieve json from the google books api
Here are my structs:
struct Book: Codable {
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Codable {
let title: String
let authors: [String]
let publisher, publishedDate, description: String
}
and this is the code to decode my json:
var bookInfo = [Book]()
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=harry+potter") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let parsedJSON = try JSONDecoder().decode(Book.self, from: data)
for books in self.bookInfo {
DispatchQueue.main.async {
print(books.volumeInfo.title)
}
}
} catch {
print(error)
}
}
}.resume()
}
}
and the error message I get is this:
keyNotFound(CodingKeys(stringValue: "volumeInfo", intValue: nil),
Swift.DecodingError.Context(codingPath: [], debugDescription: "No
value associated with key CodingKeys(stringValue: "volumeInfo",
intValue: nil) ("volumeInfo").", underlyingError: nil))
you are trying to decode a Book object, where you should be decoding the
API response, then extract the Books from that. This is why you are getting the error.
Try the following example code:
struct Book: Identifiable, Codable {
let id = UUID()
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Codable {
let title, publishedDate: String
let authors: [String]?
let publisher, description: String?
}
struct ApiResponse: Codable {
let kind: String
let totalItems: Int
let items: [Book]
}
var bookInfo = [Book]()
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=harry+potter") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let response = try JSONDecoder().decode(ApiResponse.self, from: data)
bookInfo = response.items
} catch {
print(error)
}
}
}.resume()
}
}

Swift - Simple fix for "Instance member cannot be used on type" error?

Here is the JSON "decoding"/parsing I did based on the isSteamDown JSON page:
struct Instruction: Decodable {
let statuses: [Status]
let message, messageURL: String
let status: Bool
let load, time: Int
enum CodingKeys: String, CodingKey {
case statuses, message
case messageURL = "message_url"
case status, load, time
}
}
And here is the code I wrote to try and decode it:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "https://issteamdown.com/status.json"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
do {
guard let json = (try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
return
}
for statusCheck in json {
print(Instruction.status)
}
} catch {
print(error)
}
}.resume()
}
}
The error I am having is with the print line here:
for statusCheck in json {
print(Instruction.status)
}
and the error is as follows:
Instance member 'status' cannot be used on type 'Instruction'
What I am wondering is: What is the solution to this specific issue with my code?
Also: Is there a general solution for this that usually works for this error format?
Also if it is not too much to ask, could you please explain the reasoning behind your answer in as layman of terms as possible?
EDIT: I tried changing "Instruction.status" to "statusCheck.status" and it returned the error:
Value of tuple type '(key: String, value: Any)' has no member 'status'
Status should adapt Decodable also..
And you should use JSONDecodable method like below.
struct Status: Decodable {
let title: String
let code: Int
let status: Bool
let time: Int
}
struct Instruction: Decodable {
let statuses: [Status]
let message, messageURL: String
let load: Int
enum CodingKeys: String, CodingKey {
case statuses, message
case messageURL = "message_url"
case load
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "https://issteamdown.com/status.json"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
guard let data = data else { return }
do {
let json = try JSONDecoder().decode(Instruction.self, from: data)
json.statuses.forEach { status in
print(status)
}
} catch {
print(error)
}
}.resume()
}
}
It is better to use JSONDecoder as opposed to JSONSerialization for Decodable types.
let decoder = JSONDecoder()
guard let instruction = try decoder.decode(Instruction.self, from: data!) else { return }
print(instruction.status) // no need for 'for' loop!
Here is the above code in context of your existing code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "https://issteamdown.com/status.json"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
do {
// write the above code here
} catch {
print(error)
}
}.resume()
}
}

Unable to parse JSON from URL in tableview

I am trying to pull car information from the following API.
but I can't seem to display the information in my tableview...
Any and all help is appreciated!
viewController
var hondaList: [HondaModel] = []
override func viewDidLoad() {
//let jsonUrl = "https://api.myjson.com/bins/149ex5"
let url = URL(string: "https://api.myjson.com/bins/149ex5")
URLSession.shared.dataTask(with: url!) { (data, urlrespone , error) in
do{
try self.hondaList = JSONDecoder().decode([HondaModel].self, from: data!)
for honda in self.hondaList {
print(honda.name)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch{
print( "Error in fectching from https://api.myjson.com/bins/149ex5")
}
}.resume()
super.viewDidLoad()
}
Model
import Foundation
struct HondaModel: Decodable {
let name: String
let engine: String
let transmission: String
let ocolor: String
let icolor: String
let vin: String
}
This is a very common mistake: You are ignoring the root object (and both possible errors)
Add this struct
struct Root : Decodable {
private enum CodingKeys: String, CodingKey { case results = "Results", message = "Message" }
let results : [HondaModel]
let message : String
}
and decode
if let error = error { print(error); return }
do {
let root = try JSONDecoder().decode(Root.self, from: data!)
self.hondaList = root.results
...
and please, please, print the error rather than a meaningless literal string. The error tells you what's wrong.
catch {
print(error)
}
In your case you would get
"Expected to decode Array<Any> but found a dictionary instead."
which is a very significant hint.
try this
if let resultJSON = data?["Results"] as? [[String: Any]] {
do {
let _data = try JSONSerialization.data(withJSONObject: resultJSON, options: .prettyPrinted)
self.hondaList = try JSONDecoder().decode([HondaModel].self, from: _data)
// … same thing
}
}