Dictionary in swift - swift

let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.

You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}

Related

How to initialize a case insensitive dictionary using Swift?

My problem is that this code is case-sensitive. If I have "Sam" and "sam", they will be sorted into different keys. Any way that I can think of doing this is by converting the string into all lowercase, but I want it to stay as normal while being sorted without case-sensitivity:
var dict: [String: [String]] = [:]
for string in array {
if (dict[string] != nil) {
dict[string]?.append(string)
}
else {
dict[string] = [string]
}
}
As it is right now my code would result in:
["Sam": ["Sam"], "sam", ["sam"]]
Instead of what I want:
["Sam": ["Sam", "sam"]]
How can I accomplish this?
You can use reduce(into:) method and assign each element capitalized to the result:
let array = ["Sam", "sam", "SAM"]
let dict: [String: [String]] = array.reduce(into: [:]) {
$0[$1.capitalized, default: []].append($1)
}
print(dict) // ["Sam": ["Sam", "sam", "SAM"]]
If you just want to have case insensitive keys and case sensitive values, from given array, the shortest solution could be something like this:
var dict: [String: [String]] = [:]
array.forEach { dict[$0.lowercased(), default: []] += [$0] }

swift - delete or replace <null> values from nested dictionary

I have some data from server, and I'm using Alamofire SwiftyJSON to convert it to [String: Any]. Then I'm saving it to plist using SwiftyPlistManager. The point is that SwiftyPlistManager crashed when saving <null>, so I need to replace all <null>or nilto "".
My Dictionary after Alamofire SwiftyJSON looks this way:
["info_editable": true,
"name": Android Q,
"is_message": true,
"images": [["id": 92,
"image": /media/product/102.png]],
"video_description": <null>,
"is_valid": true]
or it could be -
["info_editable": true,
"name": Android Q,
"is_message": true,
"images": <null>,
"video_description": <null>,
"is_valid": true]
I suppose to use Codable from raw data, but have no idea how to set initial value as empty string or [[]], then check if parced data is <null> and leave initial value as default.
Or is there any way to list nested dictionary to replace <null>to ""?
You can try
var dic = ["1":nil,"2":"33","3":"5444"]
let res = dic.mapValues { $0 == nil ? "" : $0 }
print(res) // ["1": Optional(""), "2": Optional("33"), "3": Optional("5444")]
for now, my best idea is -
1) stringify every value except array of dictionaries,
2) check if string content "null", then replace it with ""if true.
3) array of dictionaries, if stringified, will have 2 option - with [[ and ]](then checking every dictionary like above) or without - in case of "images": <null>,(so <null> should be replaced with[[]].
but I have about 7 requests with different data, should be parsed this strange way, and I hope to found more pretty decision.
Here's a protocolish solution, that goes recursively through dictionaries and arrays:
/// Allows clients to ask the receiver to remove any `NSNull` instances
protocol NullStripable {
func strippingNulls() -> Self
}
extension Array: NullStripable {
func strippingNulls() -> Self {
return compactMap {
switch $0 {
case let strippable as NullStripable:
// the forced cast here is necessary as the compiler sees
// `strippable` as NullStripable, as we casted it from `Element`
return (strippable.strippingNulls() as! Element)
case is NSNull:
return nil
default:
return $0
}
}
}
}
extension Dictionary: NullStripable {
func strippingNulls() -> Self {
return compactMapValues {
switch $0 {
case let strippable as NullStripable:
// the forced cast here is necessary as the compiler sees
// `strippable` as NullStripable, as we casted it from `Value`
return (strippable.strippingNulls() as! Value)
case is NSNull:
return nil
default:
return $0
}
}
}
}
Usage example:
let dict: [String: Any] = [
"items": ["info_editable": true,
"name": "Android Q",
"is_message": true,
"images": [["id": 92,
"image": "/media/product/102.png"]],
"video_description": NSNull(),
"is_valid": true],
"somethingElse": NSNull()
]
print(dict.strippingNulls())
Sample output:
["items": ["name": "Android Q", "info_editable": true, "images": [["image": "/media/product/102.png", "id": 92]], "is_message": true, "is_valid": true]]
Try this one. It will remove null and replace with blank string, without loosing key.
func removeNullFromResponse(response:NSDictionary) -> NSDictionary{
let blankString = "\"\""
let myMutableDict: NSMutableDictionary = NSMutableDictionary(dictionary: response)
var data = try? JSONSerialization.data(withJSONObject:myMutableDict, options: JSONSerialization.WritingOptions.prettyPrinted)
var strData = NSString.init(data: data!, encoding:String.Encoding.utf8.rawValue)
strData = strData?.replacingOccurrences(of: "<NULL>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "<null>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "<Null>", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "NULL", with: blankString) as NSString?
strData = strData?.replacingOccurrences(of: "null", with: blankString) as NSString?
data = strData?.data(using: String.Encoding.utf8.rawValue)!
var dictionary = NSDictionary()
do
{
dictionary = try JSONSerialization.jsonObject(with: data! , options: JSONSerialization.ReadingOptions()) as! NSDictionary
} catch {
print(error)
}
return dictionary as NSDictionary
}

Swift Firebase - Convert database snapshot into an array

I have a groups reference in firebase that looks like this:
I'm having trouble converting the list of members into an array of strings in my app.
I'm fetching the data like so:
//Reference to each group
let ref = Database.database().reference().child("groups").child(snapshot.key)
//Get the group data from the reference
ref.observeSingleEvent(of: .value, with: { (groupSnap) in
//Cast data as dictionary [String:Any]
if let dictionary = groupSnap.value as? [String: Any] {
//Parse each group object
if let group = Group.parse(snapshot.key, dictionary) {
groups.insert(group, at: 0)
}
//Escape with group array
complete(groups)
}
})
And currently parsing the data without the members:
static func parse(_ key: String, _ data: [String:Any]) -> Group? {
let name = data["name"] as! String
let category = data["category"] as! String
let owner = data["owner"] as! String
return Group(id: key, name: name, category: Group.Category(rawValue: category)!, ownerId: owner, members: nil)
}
How would I turn the members list into an array of strings for my group object?
// example data
let data = [
// "name": ...
// "category": ...
// "owner": ...
"members": [
"member1": true,
"member2": false,
"member3": true,
"member4": true
]
]
// grabbing the members element like you do in your parse function
let members = data["members"] as! [String: Bool]
let membersAsListOfStrings = Array(members.keys)
print(membersAsListOfStrings) // -> ["member4", "member1", "member3", "member2"]
let filteredMembersAsListOfStrings = Array(members.filter { $0.value }.keys)
print(filteredMembersAsListOfStrings) // -> ["member4", "member3", "member1"]
You're looking for the .keys attribute. I believe all dictionaries in Swift have this. This code ran for me fine in a playground.

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

How to access nested dictionary?

I can print this in the debugger:
(lldb) print params["message"]!
([String : String]) $R5 = 2 key/value pairs {
[0] = (key = "body", value = "iPadUser has started a new stream")
[1] = (key = "title", value = "Stream started")
}
But I am trying to figure out how to access the body and title separately.
I construct params in this way:
let recipients = ["custom_ids":[recips]]
let notificationDetails = "hello there"
let content = [
"title":title,
"body":details
]
let params: [String:Any] = [
"group_id":"stream_requested",
"recipients": recipients,
"message": content
]
print((params["message"] as! [String: Any])["title"] as! String)
You need to cast the Dictionary value as specific type, since the compiler doesn't know what to expect. (Please mind that you mustn't use force unwrap in other way than example code.)
Considering you need to fetch array values when recipients dictionary looks like this:
let recipients = ["custom_ids":["recipe1", "recipe2", "etc"]]
get to the ids like this:
guard let recipients = params["recipients"] as? [String: Any],
let customIDs = recipients["custom_ids"] as? [String]
else { return }
for id in customIDs {
print(id) // Gets your String value
}