The nested dictionary that I am using is build like this
var fDict : [String : Any] = [:]
var errors : [String : Any] = [:]
var error : [String : Any] = [:]
let base : [String : Any] = ["key": "123"]
error["error"] = base
errors["errors"] = error
fDict["profile-response"] = errors
The dictionary is looking like :
{
“profile-response“ : {
“errors” : {
“error” : {
“key“ = “123”
}
}
}
}
I have written code to update the value of key to "abc"
Code :
func replaceErrorWithCustomError( data : inout [String: Any]) {
for (key,value) in data {
if key == "key" {
data.updateValue("abc", forKey: key)
break
} else if var value = value as? [String: Any] {
replaceErrorWithCustomError(data: &value)
}
}
}
The result before update and after update remains same. Please suggest how to make changes in the current dictionary without taking another dictionary.
You can try this -
func replaceErrorWithCustomError(data: inout [String: Any]) {
func updateError(dict: inout [String: Any]) -> [String: Any] {
for (key, value) in dict {
if key == "key" {
dict.updateValue("abc", forKey: key)
break
} else if var value = value as? [String: Any] {
// This is the key change
// Result must be updated back into parent
dict[key] = updateError(dict: &value)
}
}
return dict
}
updateError(dict: &data)
}
Related
I have see function to update all value in dictionary credit to Swift replace key value in array of dictionaries with nested dictionaries
but how can I make this update function as part of extension Dictionary? and how can I call the function if I place the function under extension Dictionary?
func update(_ dict: [String: Any], set value: Any, for key: String) -> [String: Any] {
var newDict = dict
for (k, v) in newDict {
if k == key {
newDict[k] = value
} else if let subDict = v as? [String: Any] {
newDict[k] = update(subDict, set: value, for: key)
} else if let subArray = v as? [[String: Any]] {
var newArray = [[String: Any]]()
for item in subArray {
newArray.append(update(item, set: value, for: key))
}
newDict[k] = newArray
}
}
return newDict
}
I did not check if the function itself works, I just transformed it so it is an extension of a [String: Any] dictionary.
It can be used like this : newDictionary = yourDictionary.update(set: yourValue, for: yourKey).
extension [String: Any]
{
func update(set value: Any, for key: String) -> [String: Any]
{
var newDict = self
for (k, v) in newDict {
if k == key {
newDict[k] = value
} else if let subDict = v as? [String: Any] {
newDict[k] = subDict.update(set: value, for: key)
} else if let subArray = v as? [[String: Any]] {
var newArray = [[String: Any]]()
for item in subArray {
newArray.append(item.update(set: value, for: key))
}
newDict[k] = newArray
}
}
return newDict
}
}
To be complete, you could probably also write a function to update your dictionary in place, which would use a bit less memory (untested) :
extension [String: Any]
{
mutating func update(set value: Any, for key: String)
{
if self[key] != nil
{
self[key] = value
}
for (k, v) in self {
if v is [String: Any]
{
var v = v as! [String: Any]
v.update(set: value, for: key)
self[k] = v
}
else if v is [[String: Any]]
{
var newV = [[String: Any]]()
for array in v as! [[String: Any]]
{
var array = array
array.update(set: value, for: key)
newV.append(array)
}
self[k] = newV
}
}
}
}
It can be used like this : yourDictionary.update(set: yourValue, for: yourKey)
Dears,
this is my code testable in a playground:
import Foundation
import UIKit
enum Error : Swift.Error, LocalizedError {
case failedTypeCastingUITableViewCell(name: String, type: Any.Type, reuseIdentifier: String?)
var domain: String {
return ""
}
var code: Int {
return 0
}
var nserror : NSError {
return NSError(domain: self.domain,
code: self.code,
userInfo: self.userInfo)
}
// var userInfo : [String: Any]? {
// var userInfo : [String: Any] = [:]
//
// switch self {
// case .failedTypeCastingUITableViewCell(let name, let type, let reuseIdentifier):
// userInfo = [
// "param_name": name
// ,"param_type": "\(type)"
// ,"param_identifier": reuseIdentifier
// ]
// return userInfo
// }
// }
var userInfo : [String: Any]? {
var userInfo : [String: Any] = [:]
switch self {
case .failedTypeCastingUITableViewCell(let name, let type, let reuseIdentifier):
userInfo = [
"param_name": name
,"param_type": "\(type)"
]
userInfo["param_identifier"] = reuseIdentifier
return userInfo
}
}
}
print(Error.failedTypeCastingUITableViewCell(name: "", type: UITableViewCell.self, reuseIdentifier: nil).userInfo)
this is the result I get from print and is what I want to achieve with commented code:
Optional(["param_name": "", "param_type": "UITableViewCell"])
this is the result I get from commented code instead:
Optional(["param_identifier": nil, "param_type": "UITableViewCell", "param_name": ""])
I know It have to work this way, but my question is can I get rid of this in some way? ie. custom init? custom subscript?
Given the concrete example, this is certainly possible, and in a number of ways.
One way is to use key/value pairs, and filter out what is nil:
let kv = [
("param_name", name),
("param_type", "\(type)"),
("param_identifier", reuseIdentifier)
].filter{$1 != nil}.map{($0, $1!)} // <== this is your magic line
userInfo = Dictionary(uniqueKeysWithValues: kv)
Another way is with reduce:
userInfo = [
("param_name", name),
("param_type", "\(type)"),
("param_identifier", reuseIdentifier)
]
.reduce(into: [:]) { (d, kv) in d[kv.0] = kv.1 }
For more readability, you could extract that to an extension:
extension Dictionary {
init<S>(uniqueKeysWithNonNilValues seq: S)
where S : Sequence, S.Element == (Key, Value?) {
self = seq.reduce(into: [:]) { (d, kv) in d[kv.0] = kv.1 }
}
}
And then the calling code would be pretty nice IMO:
userInfo = Dictionary(uniqueKeysWithNonNilValues: [
("param_name", name),
("param_type", "\(type)"),
("param_identifier", reuseIdentifier)
])
But we could push it just a little closer to your proposed syntax, and maybe that's nicer because it feels like a Dictionary again:
extension Dictionary {
init(withNonNilValues seq: KeyValuePairs<Key, Value?>) {
self = seq.reduce(into: [:]) { (d, kv) in d[kv.0] = kv.1 }
}
}
userInfo = Dictionary(withNonNilValues: [
"param_name": name,
"param_type": "\(type)",
"param_identifier": reuseIdentifier,
])
Yes! Just don't write anything for that subscript, and when you try to access that subscript, it will automatically return nil.
Example:
myDict: [String : Any] = [
"key1" : 1,
"key2" : 2
]
let emptySubscript = myDict["key3"] // this will be nil
I am trying to fetch the "friends" from the node to be able to show them in UICollectionView afterwards. I now realized that I have to use a struct and place the Friends array inside. I am struggling now to understand how to fetch them into that array (you can see it at the bottom of the post). Data is stored in a firebase node. How can I grab the data and what would be the procedure to place it in UICollectionView afterwards? This is my function so far to retrieve.
UPDATE: (I think I am fetching correctly now but I don't get any results. Is there something that I should do in collection view? or what am I doing wrong?)
UPDATE: Here is my code for post fetching:
func fetchPosts3() {
ref.child("Users_Posts").child("\(unique)").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
if snapshot.value as? [String : AnyObject] != nil {
let allPosts = snapshot.value as! [String : AnyObject]
self.posts.removeAll()
for (_, value) in allPosts {
if let postID = value["postID"] as? String,
let userIDDD = value["userID"] as? String
{
//ACCESS FRIENDS
ref.child("Users_Posts").child("\(unique)").child(postID).child("friends").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in
print("FRIENDS: \(snap.childrenCount)")
//var routine = self.postsWithFriends[0].friends
for friendSnap in snap.children {
if let friendSnapshot = friendSnap as? DataSnapshot {
let friendDict = friendSnapshot.value as? [String: Any]
let friendName = friendDict?["name"] as? String
let friendPostID = friendDict?["postID"] as? String
let postsToShow = PostWithFriends(id: userIDDD, friends: [Friend(friendName: friendName!, friendPostID: friendPostID!)])
self.postsWithFriends.append(postsToShow)
print("COUNTING: \(self.postsWithFriends.count)")
// then do whatever you need with your friendOnPost
}
}
})
}
}
//GET LOCATION
self.collectionView?.reloadData()
self.posts.sort(by: {$0.intervalPosts! > $1.intervalPosts!})
}
})
ref.removeAllObservers()
}
That's how the data looks at the database:
{
"-LN2rl2414KAISO_qcK_" : {
"cellID" : "2",
"city" : "Reading",
"date" : "2018-09-23 00:41:26 +0000",
"friends" : {
"UJDB35HDTIdssCtZfEsMbDDmBYw2" : {
"name" : "Natalia",
"postID" : "-LN2rl2414KAISO_qcK_",
"userID" : "UJDB35HDTIdssCtZfEsMbDDmBYw2"
},
"Vyobk7hJu5OGzOe7E1fcYTbMvVI2" : {
"name" : "Gina C",
"postID" : "-LN2rl2414KAISO_qcK_",
"userID" : "Vyobk7hJu5OGzOe7E1fcYTbMvVI2"
}
},
}
}
And this is my object that's stored into array
struct PostWithFriends {
var postID : String?
var friends: [Friend]
}
class Friend : NSObject {
var friendName: String?
var friendUserID: String?
var postID: String?
init(friendName: String, friendPostID: String) {
self.friendName = friendName
self.postID = friendPostID
}
}
Replace this
if let friend = snap.value as? [String : AnyObject] {
}
With this:
for friendSnap in snap.children {
if let friendSnapshot = friendSnap as? FIRDataSnapshot {
let friendOnPost = FriendOnPost()
let friendDict = friendSnapshot.value as? [String: Any]
friendOnPost.name = friendDict?["name"] as? String
friendOnPost.friendUserID = friendDict?["userID"] as? String
friendOnPost.postID = friendDict?["postID"] as? String
// then do whatever you need with your friendOnPost
}
}
I am trying to simplify the following repetitive code:
var cells = [Cell]()
var modules = [Module]()
var fields = [Field]()
root.child("cells").observeSingleEvent(of: .value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any],
let cell = Cell(dict: dict) else { return }
cells.append(cell)
}
root.child("modules").observeSingleEvent(of: .value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any],
let module = Module(dict: dict) else { return }
modules.append(module)
}
root.child("fields").observeSingleEvent(of: .value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any],
let field = Field(dict: dict) else { return }
fields.append(field)
}
Cell, Module, Field are custom struct. I am thinking whether it's possible to put their initiators init?(dict: [String: Any]) in an array and pass in dict to each of them.
I think you will need to make your structs adopt one protocol for that:
protocol InitializableWithDictionary {
init?(dict: [String : Any])
}
struct Cell: InitializableWithDictionary {
//...
init?(dict: [String : Any]){
//...
}
}
struct Module: InitializableWithDictionary {
//...
init?(dict: [String : Any]){
//...
}
}
struct Field: InitializableWithDictionary {
//...
init?(dict: [String : Any]){
//...
}
}
var cells: [InitializableWithDictionary] = [Cell]()
var modules: [InitializableWithDictionary] = [Module]()
var fields: [InitializableWithDictionary] = [Field]()
root.child("cells").observeSingleEvent(of: .value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let array: [InitializableWithDictionary.Type] = [Cell.self, Module.self, Field.self]
array.forEach {
if let element = $0.init(dict: dict) {
switch element {
case is Cell: cells.append(element)
case is Module: modules.append(element)
case is Field: fields.append(element)
default: break
}
} else { return }
}
}
I have an arrayOfDictionaries that has been passed to a Table View Controller from a prepareForSegue.
I want to extract the key, value pairs and hold them in a new arrayToDisplay object.
I have initialized my collection data model in my viewWillAppear but I could use some help with my loadData function and how to put data into new arrayToDisplay
Table View Controller:
// data model
var collection: Collection!
var arrayToDisplay = [String: AnyObject]()
// data from previous controller, data is there
var arrayOfDictionaries = [[String: AnyObject]]()
viewWillAppear()
collection = Collection(id: "", photo: "", one: 0, two: 0, three: 0)
loadData()
func loadData() {
for dict in arrayOfDictionaries {
for (key, value) in dict {
if key == "photo" {
self.collection!.photo = value as! String
arrayToDisplay += [key: value]
}
else if key == "id" {
self.collection!.id = value as! String
arrayToDisplay += [key: value]
}
else if key == "one" {
...
One problem I have encountered is that the "key" does not change when it changes from one dictionary to the next.
Any help?
Thanks
I was able to solve this with a switch clause
func loadData() {
for dict in arrayofDict {
for (key, value) in dict {
switch key {
case "photo":
...
case "id""
...
}
Hope this helps someone!
I made a little tester class to try this out. Seems to be working fine. Maybe I don't understand what you expect it to do.
class ArrayDictTest {
// data from previous controller, data is there
var arrayOfDictionaries = [[String: AnyObject]]()
var arrayToDisplay = [String: AnyObject]()
func setup() {
arrayOfDictionaries.append(["photo" : "some photo 1", "id" : "199"])
arrayOfDictionaries.append(["photo" : "some photo 2", "id" : "299"])
arrayOfDictionaries.append(["photo" : "some photo 3", "id" : "399"])
}
func loadData() -> [String]
{
// capture what happened
var toReturn = [String]()
for dict in arrayOfDictionaries {
for (key, value) in dict {
print(key)
toReturn.append(key)
if key == "photo" {
// self.collection!.photo = value as! String
arrayToDisplay[key] = value
}
else if key == "id" {
// self.collection!.id = value as! String
arrayToDisplay[key] = value
}
}
}
return toReturn
}
}
let mytest = ArrayDictTest()
mytest.setup()
mytest.loadData()
What is the collection for?
remove else
if key == "photo" {
self.collection!.photo = value as! String
arrayToDisplay += [key: value]
}
if key == "id" {
self.collection!.id = value as! String
arrayToDisplay += [key: value]
}