Save array from JSON in user default and read array values back - swift

I'm retrieving a JSON dict with the following structure:
"SITES": [
{
"NAME": “ALICE LANE”,
"WEBSITEAPPID": “XYZ”
}
]
}
I'm saving that straight into user defaults like this:
UserDefaults.standard.set(JSON(dict as Any)["SITES"].stringValue, forKey: "adminSites")
I know there is data in the JSON because the below code provides two rows of array data:
if let arraySites = dict?["SITES"] as? [[String: Any]] {
for sitesDetails in arraySites {
print(sitesDetails["NAME"] as Any)
print(sitesDetails["WEBSITEAPPID"] as Any)
}
}
When I try print the user default data count, I get 0
let defaultAdminSites = defaults.object(forKey: "adminSites") as? [String] ?? [String]()
print(defaultAdminSites.count)
Why am I getting 0 results? How would get the array row details if I did for ["NAME"] and ["WEBSITEAPPID"]?

I would recommend using a model object and Codable to avoid all those casts:
struct Model: Codable {
let sites: [Site]
enum CodingKeys: String, CodingKey {
case sites = "SITES"
}
}
struct Site: Codable {
let name, websiteAppid: String
enum CodingKeys: String, CodingKey {
case name = "NAME"
case websiteAppid = "WEBSITEAPPID"
}
}
// write to defaults
let model = Model(sites: [Site(name: "foo", websiteAppid: "bar")])
do {
let siteData = try JSONEncoder().encode(model)
UserDefaults.standard.set(siteData, forKey: "adminSites")
} catch {
print(error)
}
// read from defaults
if let siteData = UserDefaults.standard.data(forKey: "adminSites") {
do {
let model = try JSONDecoder().decode(Model.self, from: siteData)
for site in model.sites {
print(site.name, site.websiteAppid)
}
} catch {
print(error)
}
}

UserDefault Save Json Response
let result = jsonDict["SITES"] as? NSArray ?? []
let data = try! NSKeyedArchiver.archivedData(withRootObject: result, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: "adminSites")
Get UserDefault data
let result = UserDefaults.standard.data(forKey: "adminSites")
if result != nil{
let data = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(result!) as? NSArray ?? []
print("Current User Details : - \(data)")
}

Related

Update a value on stuct codable identifiable and only userdefault cache this value

I have 2 structs codable : Student and Adress (which is linked to Student)
On my app, I fetch data from Firebase RTDB and then I store it with userdefault
Let's say a student changes his email and I just want to update the stored userdefault only with that updated email.
Do I need to specify all other data when I want to store it(name,adress,dob...) or can I just only update/store the email on my userdefault without specifying the other data ?
struct Adress : Codable, Identifiable {
var id: String { Student_UID }
var Student_UID: String
var Student_Street: String
var Student_Country: String
var Student_City: String
enum CodingKeys: String, CodingKey {
case Student_UID = "Adress_Student_UID"
case Student_Street = "Student_Street"
case Student_Country = "Student_Country"
case Student_City = "Student_City"
}
}
struct Student_Profile_Data:Codable, Identifiable {
var id: String { Student_UID }
var Student_UID: String
var Student_firstName: String?
var Student_username : String?
var Student_Email : String?
var Student_lastName: String
var Student_Address: Adress?
var Student_DOB: String?
var Student_Studentpoints : Int?
enum CodingKeys: String, CodingKey {
case Student_UID = "Student_UID"
case Student_firstName = "Student_firstName"
case Student_username = "Student_username"
case Student_Email = "Student_Email"
case Student_lastName = "Student_lastName"
case Student_Address = "Student_Address"
case Student_DOB = "Student_DOB"
case Student_Studentpoints = "Student_Studentpoints"
}
}
The USERDEfault part:
//READ
NSLog("userdefault TEST READ")
let defaults = UserDefaults.standard
if let savedStudent = defaults.object(forKey: "SavedStudent") as? Data {
let decoder = JSONDecoder()
if let loadedStudent = try? decoder.decode(Student_Profile_Data.self, from: savedStudent) {
NSLog("TEST PROFILE- username : \(loadedStudent.Student_username)")
}}
//WRITE
let add1 = Adress(Student_UID: "", Student_Street: "", Student_Country: "", Student_City: "")
let stud = Student_Profile_Data(Student_firstName: "", Student_username: "Martin", Student_Email: "", Student_lastName: "", Student_Address: add1, Student_DOB: "", Student_Studentpoints: 0, Student_UID: "")
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(stud) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "SavedStudent")
NSLog("WRITE OK »)
}
//UPDATE ONE (OR TWO) VALUES ?
If you are storing the student in UserDefault in this way, then updating it would involve the three-step process of reading the student, updating its value, then writing it back.
// read
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
var savedStudent = defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
// update
savedStudent.firstName = "John"
// write
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(savedStudent) {
defaults.set(encoded, forKey: "SavedStudent")
}
To make this more convenient, you can extract this as a function:
func updateSavedStudent(updateBlock: (inout StudentProfileData) -> Void) {
// read
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
var savedStudent = defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
// update
updateBlock(&savedStudent)
// write
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(savedStudent) {
defaults.set(encoded, forKey: "SavedStudent")
}
}
// usage:
updateSavedStudent {
$0.firstName = "John"
$0.lastName = "Smith"
}
Alternatively, make a computed property for this saved student and put it in a utility class somewhere. Do note that this will encode and decode the student once for every property you update though.
static var savedStudent: StudentProfileData {
get {
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
return defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
}
set {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(newValue) {
defaults.set(encoded, forKey: "SavedStudent")
}
}
}
// usage
savedStudent.firstName = "John"

Get data from firestore and assign it to an array of dictionaries

I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}

Swift / Firestore - How do I get a single document with nested map objects and send them to a Struct?

I'm trying to get my data from a single document into a Struct for a Tableview. For some reason when getting a single document from Firestore. It comes back as a key-value pair. I thought it would be a dictionary. The same as when you fetch all documents from a collection. I have several nested maps(photo) inside of a map(photos). How do I get the nested maps and add the individual items into my Struct?
var items: [Item] = []
db.collection("items").document("aAZWjpXwo2V05rMOsQjR")
.getDocument{ (docSnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in docSnapshot!.data()! {
print("\(document)")
if let item = Item(dictionary: document){
self.items.append(item)
}
}
}
}
struct Item {
var photoUrl: String
var item_id: String
init(photoUrl: String, item_id: String) {
self.photoUrl = photoUrl
self.item_id = item_id
}
// I don't know how to use key-value pairs in Swift
// init?(dictionary: (key: String, value: Any)) {
init?(dictionary: [String: Any]) {
for photoInfo in dictionary["photos"] {
let photoData = photoInfo["photo"] as? [String: Any]
let photoUrl = photoData!["photoUrl"] as! String
let item_id = photoData!["item_id"] as! String
}
self.init(photoUrl: photoUrl!, item_id: item_id!)
}
}
I was able to get the embedded map items this way. I used [[String: Any]] to fix my issue. I guess an embedded map is treated as an array inside of an array. I'm still learning Swift. It took me over a week to figure this out. Happy coding everyone...
var items: [Item] = []
db.collection("items").document("aAZWjpXwo2V05rMOsQjR")
.getDocument{ (docSnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let document = docSnapshot.data(),
let doc = document["photos"] as? [[String: Any]] {
for photoInfo in doc {
if let item = Item(dictionary: photoInfo){
self.items.append(item)
}
}
}
}
struct Item {
var photoUrl: String
var item_id: String
init(photoUrl: String, item_id: String) {
self.photoUrl = photoUrl
self.item_id = item_id
}
// I don't know how to use key-value pairs in Swift
// init?(dictionary: (key: String, value: Any)) {
init?(dictionary: [String: Any]) {
let photoData = dictionary["photo"] as? [String: Any]
let photoUrl = photoData!["photoUrl"] as! String
let item_id = photoData!["item_id"] as! String
self.init(photoUrl: photoUrl!, item_id: item_id!)
}
}

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

How to retrieve value from Dict

I'm getting JSON format data from the server, then I convert the data format to the [String:Any].
JSON--> {
integer = 1;
length = "<null>";
object = (
"692b663b-b7d5-43-287ddaadc2ff"
);
string = "SANJEEV TREHAN";
}
Here is the code:
if let data = data{
do{
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
if let integer = json["integer"] as? Int {
DispatchQueue.main.async {
if integer == 1{
//retrieve data here
print(json)
}
else{
print("alert")
}
}
}
else{
print("no name")
}
}
after converting the data as [String: Any]:
json = `["length": <null>, "integer": 1, "string": SANJEEV TREHAN, "object": <__NSSingleObjectArrayI 0x2806acb10>(
692b663b-b7d5-43d5-daadc2ff) ]`
I want to retrieve the object key value from the json variable.
The data I want only is 692b663b-b7d54a-7dd-aadc2ff as the String
I tried many things but not getting the data which format I want.
Since you're using Swift, why not use Codable types instead? They're much easier to use and you don't have to do weird casting or testing everywhere.
struct Response: Codable {
let length: Int?
let integer: Int
let string: String
let object: SomeObject
}
struct SomeObject: Codable {
let uuid: UUID
}
do {
let response = try JSONDecoder().decode(Response.self, from: data)
} catch {
print(error)
}
Now you can now ask for the fields directly.
print(response.object.uuid)
Seems like your object key is an array of string. Here is how you can get the value.
if let yourObject = json["object"] as? [String] {
if yourObject.count != 0 {
yourObjectValue = yourObject[0]
}
}