Convert nested dictionary to create String Swift - ios

I have a dictionary which contains key and items model for the values.
var selectedDic: [String: [Item]] = [:]
Actually I am trying to make a string with dictionary key and key has multiple value separated by comma.
And if I add more values to key it should add values to specific key.
price and publisher are keys and itemId is the value.
I need this string: price:10-25;publisher:576,925,1737
Dictionary Print:
[
"price": [
Babil.Item(name: Optional("10 - 25"),
itemId: Optional("10-25"))
],
"publisher": [
Babil.Item(name: Optional("ABCD"),
itemId: Optional("576")),
Babil.Item(name: Optional("DEFG"),
itemId: Optional("925")),
Babil.Item(name: Optional("HIJK"),
itemId: Optional("1737"),
)
]
]
My code:
var itemString: [String: String] = [:]
var str: [String] = []
for (key, value) in selectedDic {
value.forEach { a in
if str.firstIndex(of: key) == nil {
str.append(a.itemId!)
}
}
let final = str.joined(separator: ",")
itemString.updateValue(final, forKey: key)
}

With:
struct Item {
let name: String?
let itemId: String?
}
let dict: [String: [Item]] = ["price": [Item(name: "10-25", itemId: "10-25")],
"publisher": [Item(name: "ABCD", itemId: "576"),
Item(name: "DEFG", itemId: "925"),
Item(name: "HIJK", itemId: "1737")]]
You could use:
var keys = ["price", "publisher"]
let reduced = keys.reduce(into: [String]()) { result, current in
guard let items = dict[current] else { return }
let itemsStr = items.compactMap {$0.itemId }.joined(separator: ",")
result.append("\(current):\(itemsStr)")
}
let finalStr = reduced.joined(separator: ";")
print(finalStr)
The idea:
Iterate over the needed keys (and order guaranteed), construct for each keys, the itemsIds list joined by "," and then append that with the key.
Finally, joined all that.
Add-on questions:
Why is name and itemId optional? Is that normal?
Site note: giving the first part (easily reproducible input) can increase the change of answers, so we don't have to recreate ourselves fake data to check our answers.

Try this:
The order of the keys can be different since it is a dictionary and order isn't signficant in a dict
print(
yourDict.map { (key, values) -> String in
"\(key): \(values.compactMap { $0.itemId }.joined(separator: ","))"
}.joined(separator: ";")
)

Related

Flatten an array of dictionaries to one dictionary

I am having an array of dictionaries with columnId and columnValue as a pair. Now i need to flatten it as columnId as the key and columnValue as the value of it. How is it possible to do with swift higher order functions?
let arrayOfDictionaries = [["columnId": 123, "columnValue": "sample text"], ["columnId": 124, "columnValue": 9977332]]
//The end result should be:
flattenedDictionary: [String: Any] = ["123": "sample text", "124": 9977332]
Note: Result dictionary will be in the form of [String: Any]
This would work:
func flatten(_ pairs: [[String: Any]]) -> [String: Any] {
pairs.reduce(into: [String: Any]()) {
if let id = $1["columnId"] as? Int, let value = $1["columnValue"] {
$0["\(id)"] = value
}
}
}
You can do this in two steps;
Convert your input array into a sequence of key-value pairs using compactMap
Convert the sequence back into a dictionary using Dictionary(uniqueKeysWithValues:)
let arrayOfDictionaries = [["columnId": 123, "columnValue": "sample text"], ["columnId": 124, "columnValue": 9977332]]
let tupleArray:[(String,Any)] = arrayOfDictionaries.compactMap { dict in
guard let id = dict["columnId"], let value = dict["columnValue"] else {
return nil
}
return ("\(id)",value)
}
let flattenedDictionary: [String: Any] = Dictionary(uniqueKeysWithValues: tupleArray)
Note that this code will throw an exception if there are duplicate keys. You should either take steps to ensure the columnId values are unique or use Dictionary(keysAndValues:, uniquingKeysWith:) to resolve id clashes.

Iterate through Dictionary over all levels

I have a data structure in a Dictionary which looks like this:
- device
- type
- isActive
- Data
- Manufacturer
- Build Date
- Power
...
- Example
Now if I create a For Loop it only shows me the Values of the First level which means
type, isActive, Data, Example
for (key, value) in value {
}
All below Data like Build Date or Power is Missing - how can I irritate through this levels also?
Assuming you have Dictionary with type [String:Any], function to flatten it will be something like:
func flatten(_ obj:[String:Any]) -> [String:Any] {
var result = [String:Any]()
for (key, val) in obj {
if let v = val as? [String:Any] {
result = result.merging(flatten(v), uniquingKeysWith: {
$1
})
}
//I also included initial value of internal dictionary
/if you don't need initial value put next line in 'else'
result[key] = val
}
return result
}
To use that:
let d:[String:Any] = [
"test1":1,
"test2":2,
"test3":[
"test3.1":31,
"test3.2":32
]
]
let res = flatten(d)
print(res)
["test2": 2, "test3.2": 32, "test3.1": 31, "test1": 1, "test3":
["test3.2": 32, "test3.1": 31]]
note: dictionaries are not sorted structures
You are dealing with a Recursion problem here.
You could walk the dictionary level by level:
func walk(dictionary: [String: Any]) {
for (key, value) in dictionary {
print("\(key): \(value)")
if let value = value as? [String: Any] {
walk(dictionary: value)
}
}
}
You can change [String: Any] type with your dictionary type (NSDictionary, etc.)
As Tomas says, this can be solved with recursion. His "walk" function simply prints the values on separate lines, but with no keys, and not formatting. Here is code that logs nested dictionaries using the structure you outlined:
//Define the keys we use in our example
enum keys: String {
case device
case type
case isActive
case Data
case Manufacturer
case BuildDate
case Power
case Example
}
//Create a sample dictionary using those keys and some random values
let dict = [keys.device.rawValue:
[keys.type.rawValue: "appliance",
keys.isActive.rawValue: true,
keys.Data.rawValue:
[keys.Manufacturer.rawValue: "GE",
keys.BuildDate.rawValue: "12/23/96",
keys.Power.rawValue: 23,
],
keys.Example.rawValue: "Foo"
]
]
//Create an extension of Dictionary for dictionaries with String Keys.
extension Dictionary where Key == String {
//Define a function to recursively build a description of our dictionary
private func descriptionOfDict(_ aDict: [String: Any], level: Int = 0) -> String {
var output = ""
var indent: String = ""
if level > 0 {
output += "\n"
for _ in 1...level {
indent += " "
}
}
for (key,value) in aDict {
output += indent + key + ": "
if let subDict = value as? [String: Any] {
//If the value for this key is another dictionary, log that recursively
output += descriptionOfDict(subDict, level: level+1)
} else {
if let aString = value as? String {
output += "\"" + aString + "\"\n"
} else {
output += String(describing: value) + "\n"
}
}
}
return output
}
//Add a description property for dictionaries
var description: String {
return descriptionOfDict(self)
}
}
print(dict.description)
That outputs:
device:
isActive: true
Data:
Manufacturer: "GE"
Power: 23
BuildDate: "12/23/96"
Example: "Foo"
type: "appliance"
Edit:
The above, defining a String property description, changes the output when you print a [String:Any] dictionary. If you don't want that, rename the property description to something else like dictDescription

How to convert a class / struct into dictionary with key different as that of property name

I have a struct and I want to convert that into Dictionary with properties as Key and their value as Value but the key should not be the property name but something different i.e. shopping_list_id OR shopping_list_name.
struct AddItemToEvent {
var key: String
var shoppingListId: String //shopping_list_id
var shoppingListName: String //shopping_list_name
var commonName: String
var storeCode: String
var sourceType: String
var sourceId: String
var isDefaultList: String
var storeLocation: String
// shopping_list_id, shopping_list_name, common_name, store_code, source_type (offer, promotion, normal, favourite), source_id, default_list, store_location, client_id
}
I need to have a method for struct getDictionaryEquivalent() which can result something like ["shopping_list_name":"", "shopping_list_id": "", ......]
You can use a regular expression to convert to snake case and use reflection (Mirror) to convert to a dictionary.
The regex is rather simple and will not work so well if you for instance have several uppercase letters after each other so this part could be improved if needed.
func snakeKeyDictionary(_ mirror: Mirror) -> [String: Any] {
var dictionary = [String: Any]()
for (key, value) in mirror.children {
if let key = key {
let snakeKey = key.replacingOccurrences(of: #"[A-Z]"#, with: "_$0", options: .regularExpression).lowercased()
dictionary[snakeKey] = value
}
}
return dictionary
}
Example if usage
let item = AddItemToEvent(key: "1", shoppingListId: "12", shoppingListName: "List",
commonName: "some", storeCode: "ABC", sourceType: "A", sourceId: "fgd",
isDefaultList: "yes", storeLocation: "home")
let mirror = Mirror(reflecting: item)
print(snakeKeyDictionary(mirror))
prints
["common_name": "some", "is_default_list": "yes", "store_code": "ABC", "store_location": "home", "key": "1", "shopping_list_name": "List", "source_id": "fgd", "shopping_list_id": "12", "source_type": "A"]
But of course if the goal is to create json data it is quite simple
Make the struct conform to Codable and then set the keyEncodingStrategy property when encoding
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase

Create Dictionary from Array in Swift 5

I need to create a dictionary from array with custom type for first index of the array.
Sample array : ["ABC","ZYZ","123"]
Required result : [{"name" : "ABC", "type:"A"},{"name" : "ZYZ", "type:"B"},{"name" : "123", "type:"B"}]
Note type A for first index.
My code
for url in urlArray {
urlDict["name"] = url
}
You can do a map, and then individually change the type of the first dictionary:
var dicts = urlArray.map { ["name": $0, "type": "B"] }
dicts[0]["type"] = "A"
Seeing how all your dictionary keys are all the same, and that you are sending this to a server, a Codable struct might be a better choice.
struct NameThisProperly : Codable {
var name: String
var type: String
}
var result = urlArray.map { NameThisProperly(name: $0, type: "B") }
result[0].type = "A"
do {
let data = try JSONDecoder().encode(result)
// you can now send this data to server
} catch let error {
...
}
I suppose you can use a high order function such as map or reduce
Here is an example using reduce
var array = ["ABC","ZYZ","123"]
var result = array.reduce([[String: String]](), { (previous, current) -> [[String: String]] in
let type = previous.count == 0 ? "A" : "B"
let dictForCurrent = [
"name": current,
"type": type
]
return previous + [dictForCurrent]
})
print(result)
The result:
[["type": "A", "name": "ABC"], ["type": "B", "name": "ZYZ"], ["name":
"123", "type": "B"]]
Use reduce to convert array to dictionary:
let resultDict: [String: String]
= array.reduce(into: [:]) { dict, url in
dict["name"] = url
}
The result will look like:
[
"name": URL1,
"name": URL2
]
Use map(_:) to convert each element of the array to dictionary like so,
let arr = ["ABC","ZYZ","123"]
let result = arr.map { (element) -> [String:String] in
var dict = [String:String]()
dict["name"] = element
if let char = element.first {
dict["type"] = String(char)
}
return dict
}
print(result)
since you are concern about the index, my approach will be using enumerated() which gives out the index
let array = ["ABC","ZYZ","123"]
var results: [[String: String]] = []
for (i, content) in array.enumerated() {
let type: String = i == 0 ? "A" : "B"
results.append(["name": content, "type": type])
}
print(result)
// [["type": "A", "name": "ABC"], ["name": "ZYZ", "type": "B"], ["type": "B", "name": "123"]]

how to create json object in ios swift

I have 3 arrays when I insert data inside table than that data also add in the array (key, value pair).
var person = ["ABC","XYZ","PQR"]
var email = ["abc#yahoo.com","xyz#yahoo.com","pqr#yahoo.com"]
var mobile = ["1234567890","1234567890","1234567890"]
My problem is how to create JSON object and data store key value pair.
I want this
{
"blogs": [
{
"person": "ABC",
"email": "abc#yahoo.com",
"contact": "1234567890"
},
{
"person": "XYZ",
"email": "xyz#yahoo.com",
"contact": "1234567890"
},
{
"person": "PQR",
"email": "pqr#yahoo.com",
"contact": "1234567890"
}
]
}
so that data passes to url()
In the action button that adds data in array and table
#IBAction func meeting_info(_ sender: Any) {
var PersonName = person_name.text
var Email = email_id.text
var MobileNo = mobile_no.text
if (person_name.text?.isEmpty)! || (email_id.text?.isEmpty)! || (mobile_no.text?.isEmpty)! {
displayMyAlertMessage(userMessage: "please check field empty or not");
}
else{
person.append(person_name.text!)
email.append(email_id.text!)
mobile.append(mobile_no.text!)
meetingTableView.reloadData()
}
}
I want to generate JSON array from person, email and contact in key value pairs
to answer your question.
var person = ["ABC","XYZ","PQR"]
var email = ["abc#yahoo.com","xyz#yahoo.com","pqr#yahoo.com"]
var mobile = ["1234567890","1234567890","1234567890"]
var paramCollection = [Any]()
var index = 0
for personData in person {
var dataCollection = [String:Any]()
dataCollection["person"] = personData
dataCollection["email"] = email[index]
dataCollection["contact"] = mobile[index]
paramCollection.append(dataCollection)
index += 1
}
let finalParameter = ["blogs":paramCollection]
}
//This will do the trick but to make it more robust you should rethink your design
// maybe use struct to store a persons data
struct Blog {
var person: String
var email: String
var mobile: String
init(name:String, email:String, phone:String) {
self.person = name
self.email = email
self.mobile = phone
}
}
//and instead of having three arrays holding three different property, you can have one array of
var blogArray = [Blog]()
//You understand where I'm going with this
This is not a great design by choice to have multiple arrays relating to the data of same Entity.
Ideally create an Entity Model called Blog with fields like personName, email, mobileNo like below -
struct Blog {
var personName: String?
var email: String?
var mobileNo: String?
}
And then in your code have an array of this to save the data then you can directly convert it into Json using the link
Convert Custom Structs to Json
Try this:
let jsonObject: [String: Any]?
let array: [[String: Any]] = [[:]]
for i in 0..person.count {
let dict = ["person": person[i],
"email": email[i],
"contact": mobile[i]]
array.append(dict)
}
jsonObject = ["blogs": array]
let validateJson = JSONSerialization.isValidJSONObject(jsonObject)
if validateJson {
//Go Ahead
}
let dictionary = ["key1": "value1", "key2": "value2"]
let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
// Verifying it worked:
let parsedObject = try! JSONSerialization.jsonObject(with: jsonData!, options: .allowFragments)

Resources