How to display parsed JSON data in UI? - arrays

I have parsed data from a JSON object that I received from an API call. As of now, I can print the JSON data in the debugger console, however I am trying to convert the parsed JSON back into data that can be displayed in the UI. I have two models, and an example of a JSON object looks as such:
{
"query": "milk",
"sort": "relevance",
"responseGroup": "base",
"totalResults": 693,
"start": 1,
"numItems": 10,
"items": [{
"itemId": 10291863,
"parentItemId": 10291863,
"name": "Carnation Vitamin D Added Evaporated Milk, 12 oz",
"msrp": 1.79,
"salePrice": 1.48
}]
}
I only want to display information that details the name and salePrice keys. However since the JSON is nested I don't know how to reach to that layer in order to retrieve the values. Here is my data model code:
struct Item: Codable {
let query: String
let sort: String
let responseGroup: String
let totalResults: Int
let start: Int
let numItems = 25
let items: [Product]
}
struct Product: Codable {
let name: String
let salePrice: Double
}
Code to my ViewController:
class ViewController: UIViewController {
#IBOutlet weak var itemField: UITextField!
#IBAction func filterButton(_ sender: Any) {
var components = URLComponents()
components.scheme = "http"
components.host = "api.walmartlabs.com"
components.path = "/v1/search"
let queryItemKey = URLQueryItem(name: "apiKey", value: secretKey)
var queryItemQuery = URLQueryItem(name: "query", value: itemField.text)
components.queryItems = [queryItemKey, queryItemQuery]
let searchURL = components.url
//Task to make API Network Call
guard let url = components.url else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Item object
let productData = try JSONDecoder().decode(Item.self, from: data)
print(productData)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The print log shows the following when I run my code (again, I only want to show the name and salePrice values. Is there a way I can place these values in an array or convert these values in a way I can populate my UI with? Thanks in advance.

Create a data source array
var products = [Product]()
After parsing the data assign the products array to the data source array and reload the table view
...
let productData = try JSONDecoder().decode(Item.self, from: data)
self.products = productData.items
DispatchQueue.main.async {
self.tableView.reloadData()
}
In numberOfRowsInSection return products.count
In cellForRow get the current product and assign the values to labels
let product = products[indexPath.row]
cell.textLabel!.text = product.name
cell.detailTextLabel!.text = "\(product.salePrice)"

You can try a simple loop:
for product in productData.items {
print("\(product.name) : \(product.salePrice)")
}
or in a closure way:
productData.items.forEach { print("\($0.name) : \($0.salePrice)") }
In fact your products are already in an array: productData.items
EDIT after comments
You can map your fields to get each values in separate arrays:
let names = productData.items.map { $0.name }
let salePrices = productData.items.map { $0.salePrice }

Related

Decoding JSON from API with and adding products to an array from dictionary

I am a Swift beginner and I am trying to decode JSON objects from network request. I am trying to get the products into an array to use them later for the UI. Currently I am stuck on decoding the JSON and also having them placed in an array.
Here is the simplified version of the JSON i am trying to work with (but the full JSON can be found in the url):
{
"items": [
{
"product": {
"name": "Product name"
}
}
]
}
And here is the Swift code so far.
// Models
struct Root: Codable {
let items: [Item]
}
struct Item: Codable {
let product: Product
}
struct Product: Codable {
let name: String
}
//Product array
var productArray: [Product] = []
// URL and data fetching
let urlString: String = "https://api.jsonbin.io/b/60832bec4465377a6bc6b6e6"
func fetchData() {
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
return
}
do {
let result = try JSONDecoder().decode(Root.self, from: data)
DispatchQueue.main.async {
productArray = result
}
} catch {
print(error)
}
}
task.resume()
}
fetchData()
If anyone knows how I can get past this, I greatly appreciate the help. Currently been stuck on this for a few hours, and I can't seem to figure it out. Thanks in advance!
Just map the items array
DispatchQueue.main.async {
productArray = result.items.map(\.product)
}

Issue filtering array using criteria | Swift 5

How can the following JSON array be filtered to exclude values type = quiet to only initiate viewcontrollers where test.testid type everything else.
Currently doing the following:
do {
let nav = try JSONDecoder().decode([TestStruct].self, from: data)
self.viewControllers = nav.map { test -> UIViewController in
let selected = UIImage(named: "Tab4_Large")!
let normal = UIImage(named: "Tab4_Large")!
let controller = storyboard!.instantiateViewController(withIdentifier: String(test.testid))
controller.view.backgroundColor = UIColor.white
controller.floatingTabItem = FloatingTabItem(selectedImage: selected, normalImage: normal)
return controller
}
JSON Decode Structure:
struct TestStruct: Decodable {
let testname: String
let type: String
let testid: Int
}
Need to exclude type = "quiet", but include the rest in the nav.map
Example JSON:
[
{
"testname": "Teller",
"type": "load",
"testid": 2
},
{
"testname": "Manager",
"type": "load",
"testid": 8
},
{
"testname": "Tester",
"type": "quiet",
"testid": 8
}
]
For the result that you want to achieve. First need to filter the array with the values not having type = quiet.
let filteredArray = nav.filter { $0.type != "quiet" }
then you can use compactMap to get your desired result. I am not sure about the instantiateViewController part will work the way you want.
But I am adding a simple example which I tried in Playground:
var stringData = "[{\"testname\":\"Teller\",\"type\":\"load\",\"testid\":2},{\"testname\":\"Manager\",\"type\":\"load\",\"testid\":8},{\"testname\":\"Tester\",\"type\":\"quiet\",\"testid\":8}]"
let data = stringData.data(using: .utf8)!
struct TestStruct: Decodable {
let testname: String
let type: String
let testid: Int
}
struct ViewControl {
let id: Int
}
do {
let nav = try JSONDecoder().decode([TestStruct].self, from: data)
let filteredArray = nav.filter { $0.type != "quiet" }
print(filteredArray)
let controllers = filteredArray.compactMap({ test -> ViewControl in
let viewControl = ViewControl(id: test.testid)
return viewControl
})
print(controllers)
} catch let error as NSError {
print(error)
}

How would I separate the JSON dictionary values and put them into a Swift array?

I'm am trying to return the Dictionary String values. It returns as:
categories: [yelp1.Zom2(categories: yelp1.Zom3(name: "Delivery")), yelp1.Zom2(categories: yelp1.Zom3(name: "Dine-out"))....]
How would I only return the name values such as "Delivery" and "Dine-Out"?
//yelp1 is just the name of the file
SAMPLE JSON DATA
{
"categories": [
{
"categories": {
"id": 1,
"name": "Delivery"
}
},
{
"categories": {
"id": 2,
"name": "Dine-out"
}
},
THESE ARE MY STRUCTS
struct Zom3:Codable{
let name:String
}
struct Zom2:Codable{
//let category_id:Int?
let categories: Zom3
}
struct Zom:Codable{
//let category_id:Int?
let categories: [Zom2]
}
I have tried to decode Zom3 because thats where the name value is located but it states key not found. It won't allow me to iterate through with a for loop to at least get each element of the dictionary individually.
override func viewDidLoad() {
super.viewDidLoad()
let urlName = "https://developers.zomato.com/api/v2.1/categories"
let url = URL(string: urlName)
var urlReq = URLRequest(url: url!)
urlReq.httpMethod = "GET"
urlReq.addValue("application/json", forHTTPHeaderField: "Accept")
urlReq.addValue(zomatoKey, forHTTPHeaderField: "user_key")
let task = URLSession.shared.dataTask(with: urlReq) { (data, response, error) in
guard let data = data else {return}
do {
let items = try JSONDecoder().decode( Zom.self, from: data)
print(items)
}
catch {
print(error)
}
}
task.resume()
}
From what you've written, it looks like your output is from your "print(items)" line. If that's true, then the object you've called "items" is the top level struct "Zom". To get the bottom level "name" I think you do need a for loop under "let items = try..."; (instead of "print(items)")
var arrayOfNames: [String] = []
let zoms = item.categories
for item in zoms {
let possibleName = item.categories?.name
if let name = possibleName {
arrayOfNames.append(name)
}
}
print(arrayOfNames)
If I haven't made any mistakes this should create an array of the names and print out "Delivery, Dine-Out"

Swift ObjectMapper keeps returning ambiguous without more context

I am attempting to use Swift 3 with (ObjectMapper) to map a players array from a given JSON response on a cut-down class object called "Player"; but am finding it difficult to map it.
// Sample JSON
{
"_meta": {
...
},
"fixtures": [{
...
}],
"players": [{
"name": "Smith",
"id": "15475-9524",
}]
}
However, I am finding it hard to get it to understand how to make it map properly as it always complains that it needs more context.
I am wanting my JSON consumer to get me the list of players then map all the players using Object Mapper into an array.
When I use
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: res)
It complains
Type of expression is ambiguous without more context
My class is as follows
class Player: NSObject, Mappable {
var name: String?
required init?(map: Map) {
super.init()
}
// Mappable
func mapping(map: Map) {
name <- map["name"]
}
}
I am using AlamoFire to consume the JSON.
Alamofire.request(url).responseJSON(completionHandler: {
response in
switch response.result {
case .success(let JSON):
guard let res = JSON as? [String:Any] else {
print ("Can't do this")
return
}
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: res)
print (players)
break
case .failure(let error):
print("** Request failed with error: \(error) **")
break
}
I don't quite understand how to use the ObjectMapper on the array I'm wanting to fetch.
Any assistance on this would be good.
I think you are confusing JSON Dictionary with Player's array.
Try this:
guard let res = JSON as? [String:Any] else {
print ("res:Can't do this")
return
}
guard let json_players = res["players"] as? [[String:Any]] else {
print ("json_players:Can't do this")
return
}
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: json_players)

How can I create an array of URLs with SwiftyJSON and Alamofire? [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I have the following problem I need to retrieve an array of URL's from a JSON Object in order to download all the pictures of the products from an e-commerce site in my app.
The JSON I get looks like this:
[
{
........
........
.........
........
"images": [
{
"id": 976,
"date_created": "2016-08-10T15:16:49",
"date_modified": "2016-08-10T15:16:49",
"src": "https://i2.wp.com/pixan.wpengine.com/wp-content/uploads/2016/07/canasta-familia.jpg?fit=600%2C600&ssl=1",
"name": "canasta-familia",
"alt": "",
"position": 0
}
],
.......
.......
.......
So far I've been able to get only one string from the array doing this.
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)
.responseJSON { response in
if let jsonValue = response.result.value {
let jsonObject = JSON(jsonValue)
var jsonArray = jsonObject[0]["images"][0]["src"].stringValue
print(jsonArray)
}
}
which gives me this
https://xx.xx.xx/xxxx.xxxxx.xxxx/xx-xxxxx/uploads/2016/07/canasta-familia.jpg?fit=600%2C600&ssl=1
But what I need is to access all the elements inside "images" & "src" not just the first element of the index of both.
How can I do this?
Any ideas?
Step 1:
Create a custom object to represent the pictures. We'll call this "Picture".
struct Picture {
let id:Int
let date_created:String
let date_modified:String
let src:String
let name:String
let alt:String
let position:Int
}
Step 2:
Create an array to hold all of your product pictures. Make sure you pay attention to the scope in which you create it. It should ideally outside of your download function.
var productPictures = [Picture]()
Step 3:
Download your JSON file, make a Picture struct for each image, and add each Picture to your productPictures array.
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
self.productPictures.removeAll()
guard let json = response.result.value as? [String:Any] else {
print("couldn't retrieve json as a dictionary")
return
}
guard let images = json["images"] as? [AnyObject] else {
print("there was a problem accessing the images key")
return
}
for image in images {
guard let id = image["id"] as? Int,
let date_created = image["date_created"] as? String,
let date_modified = image["date_modified"] as? String,
let src = image["src"] as? String,
let name = image["name"] as? String,
let alt = image["alt"] as? String,
let position = image["position"] as? Int
else {
print("There was a problem accessing one of the picture variables, or it was missing")
continue
}
let newPicture = Picture(id: id,
date_created: date_created,
date_modified: date_modified,
src: src,
name: name,
alt: alt,
position: position)
self.productPictures.append(newPicture)
}
case .failure(let error):
print("could not download and retrieve product images. An error occurred: \(error)")
return
}
}
Now you have an array full of Picture structs, each containing all of the necessary information pulled from your JSON download.
Note
This doesn't use SwiftyJSON, but it should work and give you the same intended result. I hope this helps!
The following lines of code should work as I've tested myself with an actual dataset.
import Alamofire
import SwiftyJSON
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)
.responseJSON { response in
if let jsonValue = response.result.value {
let jsonObject = JSON(jsonValue)
if let array = jsonObject.array {
for i in 0..<array.count {
if let images = array[i]["images"].array {
for i in 0..<images.count {
let src = images[i]["src"]
print(src) // save src in an array or whatever
}
}
}
}
}

Resources