I have the json
{
"message": null,
"data": {
"Commodity Department": {
"total": 2,
"completed": 1,
"completedWithDue": 0,
"completedWithOutDue": 1,
"inProgress": 1,
"inProgressWithDue": 0,
"inProgressWithOutDue": 1,
"statusCounter": null
}
}
I need to convert the each department json object to array. Currently each category value ("total": 0, "completed": 0, "completedWithDue": 0, "completedWithOutDue": 0, "inProgress": 0, "inProgressWithDue": 0, "inProgressWithOutDue": 0,) will be in object format. I need to convert in array and load to collectionview based on category. As of now I am trying to decode my json in the below code
public struct Dashboard: Decodable {
public let data : [String:Departments]
}
public struct Departments: Decodable {
public let total, completed, completedWithDue, completedWithOutDue: Int
public let inProgress, inProgressWithDue, inProgressWithOutDue: Int
}
let dashboard = try? JSONDecoder().decode(Dashboard.self, from: response.data!)
print(dashboard!.data.keys)
You can create an array of the values by adding a computed property to your struct
public struct Departments: Decodable {
public let total, completed, completedWithDue, completedWithOutDue: Int
public let inProgress, inProgressWithDue, inProgressWithOutDue: Int
var categoryValues: [Int] {get {
return [total, completed, completedWithDue, completedWithOutDue,
inProgress, inProgressWithDue, inProgressWithOutDue]
}
}
}
or create the array on the fly using map
dashboard?.data.mapValues{[$0.total, $0.completed, $0.completedWithDue, ...]}
You can decode json without knowing keys like this:
func printJSON(json: [String:Any]) {
let jsonKeys = json.keys //Gets the list of keys on the outer-most layer of the JSON
for i in 0..<jsonKeys.count {
let level1 = json[jsonKeys.index(jsonKeys.startIndex, offsetBy: i)] //retrieves the object with the specific keys
if let level2 = json[level1.key] as? [String:Any]{ //if the key is another object
printJSON(json: level2) //send it as a new json object to the function again
} else if let level2 = json[level1.key] as? [[String:Any]] { //if the key is an array of objects
for i in 0..<level2.count { //loop through the array
printJSON(json: level2[i]) //send each array element to the function
}
} else if let value = json[level1.key] as? String { //if value of String/Integer/Bool type
print(value) //then only print values of the specified type (String-type in this case)
}
}
}
See full article Here
Related
I have a method which is supposed to return a Set of Strings. Here is a method description:
Returns: 10 product names containing the specified string.
If there are several products with the same name, producer's name is added to product's name in the format "<producer> - <product>",
otherwise returns simply "<product>".
Can't figure out how to check if there are duplicate names in the array and then edit them as required
What I've got so far:
struct Product {
let id: String; // unique identifier
let name: String;
let producer: String;
}
protocol Shop {
func addNewProduct(product: Product) -> Bool
func deleteProduct(id: String) -> Bool
func listProductsByName(searchString: String) -> Set<String>
func listProductsByProducer(searchString: String) -> [String]
}
class ShopImpl: Shop {
private var goodsInTheShopDictionary: [String: Product] = [:]
func addNewProduct(product: Product) -> Bool {
let result = goodsInTheShopDictionary[product.id] == nil
if result {
goodsInTheShopDictionary[product.id] = product
}
return result
}
func deleteProduct(id: String) -> Bool {
let result = goodsInTheShopDictionary[id] != nil
if result {
goodsInTheShopDictionary.removeValue(forKey: id)
}
return result
}
func listProductsByName(searchString: String) -> Set<String> {
var result = Set<String>()
let searchedItems = goodsInTheShopDictionary.filter{ $0.value.name.contains(searchString) }
let resultArray = searchedItems.map{ $0.value }
result = Set(searchedItems.map{ $0.value.name })
if result.count > 10 {
result.removeFirst()
}
return result
}
}
If you want to achieve this you would need to iterate over you resultArray and save producer and product into another array. On each iteration you would need to check if the array allready contains either the product name itself or an allready modified version.
A possible implementation would look like this:
var result = [(producer: String, product: String)]()
// iterate over the first 10 results
for item in resultArray.prefix(10){
if let index = result.firstIndex(where: { _ , product in
product == item.name
}){
// the result array allready contains the exact product name
// so we need to convert the name allready in the list
let oldProduct = (producer: result[index].producer, product: "\(result[index].producer) \(result[index].product)")
result[index] = oldProduct
// add the new one
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
}
else if !result.filter({ $0.product.components(separatedBy: " ").contains(item.name)}).isEmpty {
// if the result array allready contains a modified version of the name
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
} else{
// if the result array does not contain the product yet
result.append((producer: item.producer, product: "\(item.name)"))
}
}
let productNames = result.map{ $0.product}
Please be aware: As you are using a [String: Product], which is a unsorted dictionary, to hold your values this will yield different results (if the resultArray collection is larger than 10) each time you search.
Tested with searchString = name1:
var goodsInTheShopDictionary: [String: Product] = Dictionary(uniqueKeysWithValues: (0...20).map { index in
("\(index)",Product(id: "", name: "name\(index)", producer: "producer\(index)"))
})
goodsInTheShopDictionary["100"] = Product(id: "11", name: "name1", producer: "producer11")
goodsInTheShopDictionary["101"] = Product(id: "12", name: "name1", producer: "producer12")
Result:
["name13", "producer12 name1", "name10", "name19", "producer11 name1",
"name17", "name14", "name18", "producer1 name1", "name16"]
I have array of dictionary and the values are as follows:
[["-MXpvzmZdbqzrjND8w9F": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = hi;
timeStamp = 1617960107264;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}, "-MXq5NAyrkk4ZcvRFM7T": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = "how ru?";
timeStamp = 1617962828647;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}], ["-MXqa5-pkC28lY_Q_hpZ": {
lid = "-MWwEpHAhIdhN0i5sltB";
message = "hi nice cycle";
timeStamp = 1617971142820;
title = "Cycle for kids";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}]]
Here there are 2 elements in the array. I want to take the last element in both of the array and sort it based on the timestamp value.
How to do it? Please help me.
From comments, it seems that that the type of the array is:
typealias SomeDataArray = [[String: [String: Any]]]
So we're missing type safety from the beginning. The first thing I'd do is define a struct to represent Any in that definition. For now I'll just use a struct as a wrapper for the inner dictionary, using computed properties for all the fields:
struct SomeData
{
let dict: [String: Any]
var lid: String? { dict["lid"] as? String }
var message: String? { dict["message"] as? String }
var timeStamp: Int { dict["timeStamp"] as? Int ?? 0 }
var title: String? { dict["title"] as? String }
// Skipping user, because I have no idea what to make of its type.
}
Really this should be decoded into some real Swift type, but that's a topic for another day.
So now we redo the typealias
typealias SomeDataArray = [[String: SomeData]]
In chat it was explained what within the "last" element for each of these dictionaries can be defined as the one with the largest timeStamp value. Given that this will give this solution (retaining the keys)
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
If you want to transform it back the [[String;Any]] you get from Firebase, then it would be this:
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
.map { [$0.0: $0.1.dict as Any] }
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 }
the data I'm getting from an API returns a single object but when there's multiple objects, it returns an array in the same key. With the current model (struct) I'm working with, the decoding fails when an array shows up.
These results are randomly ordered, meaning I can't know when I will be served an object or array.
Is there a way to create a model that takes this fact into account and can assign the correct type to cast for the value ('String' or '[String]') so that the decoding continues without problem?
This is an example of when an object is returned:
{
"firstFloor": {
"room": "Single Bed"
}
}
This is an example of when an array is returned (for the same key):
{
"firstFloor": {
"room": ["Double Bed", "Coffee Machine", "TV", "Tub"]
}
}
Example of the struct that should be able to be used as model to decode both samples above:
struct Hotel: Codable {
let firstFloor: Room
struct Room: Codable {
var room: String // the type has to change to either array '[String]' or object 'String' depending on the returned results
}
}
These results are randomly ordered, meaning I can't know when I will be served an object or array.
Here is the complete playground file:
import Foundation
// JSON with a single object
let jsonObject = """
{
"firstFloor": {
"room": "Single Bed"
}
}
""".data(using: .utf8)!
// JSON with an array instead of a single object
let jsonArray = """
{
"firstFloor": {
"room": ["Double Bed", "Coffee Machine", "TV", "Tub"]
}
}
""".data(using: .utf8)!
// Models
struct Hotel: Codable {
let firstFloor: Room
struct Room: Codable {
var room: String // the type has to change to either array '[String]' or object 'String' depending on the results of the API
}
}
// Decoding
let decoder = JSONDecoder()
let hotel = try decoder.decode(Hotel.self, from: jsonObject) //
print(hotel)
You might encapsulate the ambiguity of the result using an Enum with Associated Values (String and Array in this case), for example:
enum MetadataType: Codable {
case array([String])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .array(container.decode(Array.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .array(let array):
try container.encode(array)
case .string(let string):
try container.encode(string)
}
}
}
struct Hotel: Codable {
let firstFloor: Room
struct Room: Codable {
var room: MetadataType
}
}
I have a table view which displays a user's Name, Company Name and Photo (PFFile). Each tableView row I have has all of this information in it.
I am using UISearchBarDelegate and IB to implement a search function to filter by the user's Name. It is finding the correct user but I have not been able to also update the company photo.
How do I filter the other arrays? The items I need from the arrays will be at the same index as the ones taken from the user's Name array.
EDIT: I am trying a different data structure and am receiving array index out of range, updated code below:
var filterArray = [User]() //<-- globally declared
var userArray = [User]() //< Global
class User {
var name: String?
var company: String?
init (name: String?, company: String?) {
self.name = name
self.company = company
}
}
//In a class which populates the search arrays
for object in unwrappedSucceeded {
let username = object.valueForKey("username") as! String
let companyName = object.valueForKey("companyName") as! String
let user = User(name: username, company: companyName)
userArray.append(user)
}
//tableViewController
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filterArray.removeAll(keepCapacity: false)
if searchText.characters.count != 0 {
isSearch = true
self.search(searchText)
} else {
isSearch = false
}
}
func search(text: String) -> Void {
filterArray = userArray.filter({$0.name == text})
}
//In cellForRowAtIndexPath
cell.usernameCell.text = filterArray[indexPath.row].name //ARRAY INDEX OUT OF RANGE
Like I said you strongly recommend to group each user's info into one big container, therefore we could use array of struct or class, then it comes easier to filter.
schematic for the container:
struct Container
{
var username:String?
var companyName:String?
var photo:UIImage?
}
your main array will be : var arrayofData = [Container]()
Now when you are query your objects from parse, inside of your query function
// after you called the findObjectsWithBackgroundBlock()
// let's assume you check for error and if the [PFObject] is empty or not
for one in objectsFromParse
{
let photoToget = one["Photo"] as! PFFile
// next step should be to get the image data right :)
{
// let's assume that is the block when get the image data right:)
// check your data and assign it to some UIImage
// then
let userRepresentation = Container() //<-- we are creating a single object representation for each user
let username = one["username"] as! String //<--data we got from Parse
let companyName = one["companyName"] as! String
let userImage = //the UIImage which contains the data
userRepresentation.username = username
userRepresentation.companyName = companyName
userRepresentation.photo = userImage
// then we append
arrayOfData.append(userRepresentation)
}
}
Now we have all data into our array, so let's filter by username and also I hope you configure your tableView so when you have data from filter or regular array.
var filterArray = [Container]() //<-- globally declared
func search(text: String) -> Void
{
filterArray = arrayOfData.filter(){ (Container) -> Bool in
let range = Container.name!.rangeOfString(text, options:NSStringCompareOptions.CaseInsensitiveSearch) return range != nil }
// then you are good to go
}
let arr1 = [10,20,40]
let e1 = arr1.enumerate()
let arr2 = ["a","b","c"]
let f1 = e1.filter { $0.element % 20 == 0 }
let f2 = arr2.enumerate().filter { j, _ in
f1.contains { i, _ in
i == j
}
}
print(f1.map{$0.element}, f2.map{$0.element})
// [20, 40] ["b", "c"]
now you have both arrays "filtered". the best, what you can do is redesigning your data model!