Different json format for encode Swift - arrays

I try to prepare data for JSON encode:
var i = 0
for (data) in students {
var variableNewData = ["\(students[i].id)":["timestampValue":"\(students[i].timestampValue)"]]
variableNewData["updateTime"] = ["updateTime":"\(students[i].timestampValue)"]
variableNewData["createTime"] = ["createTime":"\(students[i].timestampValue)"]
i += 1
let finalParameter = ["class":variableNewData]
print("LastParameter:",finalParameter)}
}
I need data in this format:
{"class": {"studentOne": {"timestampValue": "2020-02-04" },"studentTwo ":{ "timestampValue": "2020-02-05" }},"createTime": "2020-03-30","updateTime": "2020-03-30"}
but I get this: class, id, timestampValue seems ok, but create and update time are wrong.
thanks for any suggestions.
{"class":{"createTime":{"createTime":"2020-03-30"},"studentOne":{"timestampValue":"2020-02-04"},"updateTime":{"createTime":"2020-03-30"}}}

Let’s look at your desired format in a “pretty” format:
{
"class": {
"studentOne": {
"timestampValue": "2020-02-04"
},
"studentTwo ": {
"timestampValue": "2020-02-05"
}
},
"createTime": "2020-03-30",
"updateTime": "2020-03-30"
}
So, for the key class, you have a value that is a subdictionary keyed by the student id, that itself contains yet another a dictionary with a single timestamp. So, I’d build this dictionary associated with class first:
var studentsDictionary: [String: [String: String]] = [:]
for student in students {
studentsDictionary[student.id] = ["timestampValue": student.timestampValue]
}
You then have createTime and updateTime that are at the top level, alongside class (presumably the data of creation and update of the whole class, not the individual students). Anyway you could build the top level dictionary, like so:
let dictionary: [String: Any] = [
"class": studentsDictionary,
"updateTime": "2020-02-05",
"createTime": "2020-02-05"
]
Obviously, you’d want to set updateTime and createTime for timestamp values for the class, but hopefully that illustrates the idea.
And we could then build the JSON representation of all of this with:
let data = try! JSONSerialization.data(withJSONObject: dictionary) // add `option: .prettyPrinted` if you want to see pretty version
//
// if you want to check the above `data`:
//
// let string = String(data: data, encoding: .utf8)!
// print(string)
//
Note, that the updateTime and createTime are not at the student level, so I’m not sure where you wanted to get those values from.
By the way, if you’re interested, a more concise way to build that studentDictionary dictionary is with the Dictionary(uniqueKeysWithValues:):
let studentsDictionary = Dictionary(uniqueKeysWithValues: students.map { student in
(student.id, ["timestampValue": student.timestampValue])
})

Try setting the updateTime and createTime key values as simple strings instead of dictionaries of their own.
variableNewData["updateTime"] = "\(students[i].timestampValue)"
variableNewData["createTime"] = "\(students[i].timestampValue)"

Related

Weekly activity summary help? SWIFTUI

I'm tryin' to obtain a list of activities ("dd/mm/YY: goal achieved/missed goal") which has to be setted every week. The problem is that I obtain a list of activities with the same date and the same result of the previous one. For example:
28/02/2022: goal achieved
28/02/2022: goal achieved
28/02/2022: goal achieved
and the next day:
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
I want to obtain, instead, a list like:
28/02/2022: goal achieved
01/03/2022: missed goal
02/03/2022: goal achieved...
These are useful structs:
struct Persistent {
#AppStorage("goalAchieved") static var goalAchieved : Bool = false
#AppStorage("activityList") static var activityList : [String] = []
}
struct obj {
static var currentDate = Date()
static var stringDate = ""
static var activity = Activity(date:Persistent.lastUpdatedDate)
}
This is the ActivityListView:
import SwiftUI
func activitystring(activity:Activity) -> String{
var output = ""
output = "\(activity.date): \(activity.reachedobj(goalAchieved: Persistent.goalAchieved))"
return output
}
struct Activity: Identifiable{
let id = UUID()
let date: String
func reachedobj(goalAchieved: Bool) -> String {
var output = ""
if Persistent.goalAchieved == false { output = "Missed goal" }
if Persistent.goalAchieved == true { output = "Goal Achieved!"}
return output
}
}
struct ActivityRow: View{
var activity: Activity
var body: some View{
Text(activitystring(activity: activity))
Divider()
}
}
struct ActivityListView: View {
var body: some View {
ScrollView{
Text("Week summary").font(.system(size: 15)).foregroundColor(Color.green)
Text("")
ForEach(Persistent.activityList, id: \.self) { activity in
let activity = Activity(date: Persistent.lastUpdatedDate)
ActivityRow(activity: activity)
}
}
}
}
Finally this is the useful code in the ApplicationApp file (main) where I update activity list:
MenuView().onAppear(){
if Persistent.activityList.count>7{
Persistent.activityList.removeAll()
}
obj.currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
obj.stringDate = formatter.string(from:obj.currentDate)
if Persistent.lastUpdatedDate != obj.stringDate{
Persistent.goalAchieved = false
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList.append(activitystring(activity: activity))
Persistent.lastUpdatedDate = obj.stringDate
}
}
What's wrong on this?
You're calling obj.activity in your ForEach and ActivityRow, that's why it repeats that same static property all over the place.
You better just drop your struct obj and try again without it
In your Persistent object you have an array of many activities, called activitylist , but one single boolean that tells if the goal is achieved - goalachieved indeed.
Your view is iterating through the array of Persistent.activitylist, so you will have many lines for one single result - achieved or not achieved. You might actually want to iterate over an array of Persistent objects - meaning that somewhere you should probably store [Persistent] in some variable. In this way, you will see one line only for each result.
If I also may suggest: use the conventions for naming variables, Swift uses "camelCaseConventionForVariables", easier to read than "thewholevariableislowercase"
Edit:
Let me try to change a little bit your code (I would personally change it more radically, but that's not the scope of the answer).
Instead of having only one goalAchieved for all elements on the array activityList, make it a dictionary:
struct Persistent {
// Drop this variable
// #AppStorage("goalAchieved") static var goalAchieved : Bool = false
// Make this a dictionary, the date will be the key and the goalAchieved will be the value
#AppStorage("activityList") static var activityList : [String: Bool] = [:]
}
Add values to the dictionary (#meomeomeo is right, you don't need obj):
MenuView().onAppear() {
if Persistent.activityList.count > 7 {
Persistent.activityList.removeAll()
}
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
let stringDate = formatter.string(from: currentDate)
if Persistent.lastUpdatedDate != stringDate {
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList[activitystring(activity: activity))] = false // Will create something like ["01/03/2022": false]
Persistent.lastUpdatedDate = stringDate
}
}
Iterate on the dictionary in your ForEach; for more info: read here.

Swift, get data from [String: Any] type value

I have a value like this;
["id1": {
name = "iphone 6s";
price = 330;
}, "id2": {
name = iphone7s;
price = 500;
}]
I dont know what the id's are. But I need to get the name and the price data from that data.
EDIT: Here is the getting value code;
database.child("SortedItems").child("byName").observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value as? [String: Any] else {
return
}
Assuming your data structure matches what you have quoted at the top, you should be able to cast your first line as:
guard let data = snapshot.value as? [String:[String:Any]]
If not, t would be helpful to see what the console looks like if you print snapshot.value
Then, the following from my previous answer should work:
let ids = data.keys //get just the ids/keys
//iterate through and print the name and price once they're cast to the correct types
data.forEach { (key,value) in
if let name = value["name"] as? String, let price = value["price"] as? Int {
print(name)
print(price)
}
}
//declare a model type that conforms to Codable
struct Phone : Codable {
var name : String
var price : Int
}
data.forEach { (key, value) in
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
//decode the Phone model
let phoneModel = try JSONDecoder().decode(Phone.self, from: jsonData)
print(phoneModel)
} catch {
//handle error
}
}
You may also want to look at the Firebase docs, which give some more detail about getting your data, including converting to custom objects: https://firebase.google.com/docs/firestore/query-data/get-data

How can i add specific JSON value to another array in Swift?

I'm new in IOS programming.
I have a json array described with code below.
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as?
NSDictionary
print("json: \(String(describing: json))")
Output of code is;
json: Optional({
vendors = (
{
firm = "XXX firm";
id = 1;
"show_firm" = 1;
},
{
firm = "ZZZZZ firm";
id = 2;
"show_firm" = 1;
}
);
})
I want to add only firm values to another array like firms = ["XXX firm" , "ZZZZZ firm"]
How can I do that?
Any help would be greatly appreciated.
#witek bobrowski asked String(data: data!, encoding: .utf8) output.This output is below also. By the way json data comes from server as http post response.
json2: Optional("{\"vendors\":[{\"id\":\"1\",\"firm\":\"XXX firm\",\"show_firm\":\"1\"},{\"id\":\"2\",\"firm\":\"ZZZZZ firm\",\"show_firm\":\"1\"}]}")
I believe the best way to go is to decode the JSON and then add the firms value to an array.
struct model: Decodable{
var vendors: [decodingModel]
}
struct decodingModel: Decodable{
var firm: String
var id: Int
var show_firm: Int
}
let decoder = JSONDecoder()
do{
let result = try decoder.decode(model.self, from: jsonData)
let firmsArray = result.vendors.compactMap({$0.firm})
}catch{
print(error)
}
Since you have not posted your son structure, I can only assume you have a Json where vendor is an array of jsons. firmsArray is what you are looking for.
If this doesn't work is probably because of the wrong model and decodingModel. If you post your json structure, I will update the code so that you can properly decode your json
the best way is to create Decodable Model for your json as below:
struct Firm: Decodable {
let id: Int
let name: String
let showFirm: Int
enum CodingKeys: String, CodingKey {
case id
case name = "firm"
case showFirm = "show_firm"
}
}
I created this factory method to simulate your json response locally based on what you provided in the question
struct FirmFactory {
static func makeFirms() -> [Firm]? {
let json = [
[
"firm": "XXX firm",
"id": 1,
"show_firm": 1,
],
[
"firm": "ZZZZZ firm",
"id": 2,
"show_firm": 1,
],
]
// you should use the following code to decode and parse your real json response
do {
let data = try JSONSerialization.data(
withJSONObject: json,
options: .prettyPrinted
)
return try JSONDecoder().decode([Firm].self, from: data)
} catch {
print("error \(error.localizedDescription)")
return nil
}
}
}
now you will be able to map only the firm names as you request you can test like this
let firmNames = FirmFactory.makeFirms()?.map { $0.name }
print("firmNames \(firmNames)")
I answered my own question again. There are 2 answers given but i didn't use any of these in my code. May be these two answers are usable but because of i'm new in IOS i couldn't use any of them. At the end of long google search i solved my problem as below.
let vendors = json!["vendors"]! as! [[String : AnyObject]]
for firm in vendors {
let firm1 = firm["firm"]! as! String
self.providerArray.append(firm1)
}
I hope this answer solves someone else's problem like me.

How to display parsed JSON data in UI?

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 }

How to group Array of Dictionaries by a key in swift?

For example, I have this array of dictionaries
[["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]
I want to group them by Country to become
{"Egypt": [{"Country":"Egypt","Name":"Mustafa","Age":"20"} {"Country":"Egypt","Name":"Ali","Age":"40"}],
"Palestine": [{"Country":"Palestine","Name":"Amani","Age":"30"},{"Country":"Palestine","Name":"Omar","Age":"15"}],
"Jordan":[{"Country":"Jordan","Name":"Ahmad","Age":"25"},{"Country":"Jordan","Name":"Mustafa","Age":"20"}]
}
Please help.
Swift has a nice function that does this for you...
let people = [["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]
let peopleByCountry = Dictionary(grouping: people, by: { $0["Country"]! } )
peopleByCountry will now be the format that you want.
You can read more about this function in the documentation.
Just to add to Hamish's comment.
You really shouldn't be working with Dictionaries here. You should be working with Structs...
struct Person {
let countryName: String
let name: String
let age: Int
}
Even better would be to have a Country struct...
struct Country {
let name: String
}
and use that in the Person for their country property instead of String.
let arrCountry: [[String:String]] = [["Country":"Egypt","Name":"Mustafa","Age":"20"],
["Country":"Palestine","Name":"Omar","Age":"15"],
["Country":"Egypt","Name":"Ali","Age":"40"],
["Country":"Jordan","Name":"Ahmad","Age":"25"],
["Country":"Palestine","Name":"Amani","Age":"30"],
["Country":"Jordan","Name":"Mustafa","Age":"20"]]
func sortCountry() {
var sortedCountries : [String : [[String:String]]] = [:]
for object in arrCountry {
let country = object["Country"] as! String
if var arrCountry = sortedCountries[country] {
arrCountry.append(object)
sortedCountries[country] = arrCountry
}
else {
sortedCountries[country] = [object]
}
}
}
Well I would go like this:
Get all the countries by traversing the array once and store it in an array.
Loop for this array of countries.
Filter the array with predicate where country is current country.
Store this in the final dictionary of country.

Resources