Convert Firdatasnapshot to Custom Type

Firebase converting snapshot value to objects

Try using the class, protocol and extension I have created below, it will save you a lot of time trying to map the snapshots to objects.

//
// FIRDataObject.swift
//
// Created by Callam Poynter on 24/06/2016.
//

import Firebase

class FIRDataObject: NSObject {

let snapshot: FIRDataSnapshot
var key: String { return snapshot.key }
var ref: FIRDatabaseReference { return snapshot.ref }

required init(snapshot: FIRDataSnapshot) {

self.snapshot = snapshot

super.init()

for child in in snapshot.children.allObjects as? [FIRDataSnapshot] ?? [] {
if respondsToSelector(Selector(child.key)) {
setValue(child.value, forKey: child.key)
}
}
}
}

protocol FIRDatabaseReferenceable {
var ref: FIRDatabaseReference { get }
}

extension FIRDatabaseReferenceable {
var ref: FIRDatabaseReference {
return FIRDatabase.database().reference()
}
}

Now you can create a model that inherits the FIRDataObject class and can be initialised with a FIRDataSnapshot. Then add the FIRDatabaseReferenceable protocol to your ViewController to get access to your base reference.

import Firebase
import UIKit

class ViewController: UIViewController, FIRDatabaseReferenceable {

var posts: [Post] = []

override func viewDidLoad() {

super.viewDidLoad()

ref.child("posts").observeEventType(.ChildAdded, withBlock: {
self.posts.append(Post(snapshot: $0))
})
}
}

class Post: FIRDataObject {

var author: String = ""
var body: String = ""
var imageURL: String = ""
}

UPDATE for Swift 3

class FIRDataObject: NSObject {

let snapshot: FIRDataSnapshot
var key: String { return snapshot.key }
var ref: FIRDatabaseReference { return snapshot.ref }

required init(snapshot: FIRDataSnapshot) {

self.snapshot = snapshot

super.init()

for child in snapshot.children.allObjects as? [FIRDataSnapshot] ?? [] {
if responds(to: Selector(child.key)) {
setValue(child.value, forKey: child.key)
}
}
}
}

How to convert FIRDataSnapshot in array of dictionaries? Swift 3

What you need is to just use toAnyObject() method to get the dictionary object from your FireBaseData like this.

let arrayOfDictionary = self.bookingInfo.flatMap { $0.toAnyObject() as? [String:Any] }

You will get your desired result in arrayOfDictionary it is type of [[String:Any]]

Swift Firebase Snapshot to object model


//this goes into your call (observeSingleEvent)
let enumerator = snapshot.children //assuming you use snapshot as name
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
// this loops through every child in that map
let values = (rest as! DataSnapshot).value as? NSDictionary
let coins= values?["coins"] as? Int ?? 0
//above code looks for a key with username and grabs the value from that. If it is not a string value it returns the default value.
//use above code for picture 1
if (rest as! DataSnapshot).key == "slot"{
let enumeratorMap1 = (rest as! DataSnapshot).children
while let rest2 = enumeratorMap1.nextObject() as? FIRDataSnapshot {
let valuesMap1 = (rest2 as! DataSnapshot).value as? NSDictionary
//loop through values in new map
//use this methodes for looping through maps, as stated in picture 2
//keep repeating this method for any child under the map
}
}
}

Picture 1:
Sample Image

Picture 2:
Sample Image

How to Parse a Single Dictionary from Firebase Snapshot?

This is the code provided in your question:

_ = DataService.ds.REF_POSTS.child(key!).observe(.value, with: { (snapshot) in  

if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print("Zhenya: here is the snap: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(postKey: key, postData: postDict)
self.posts.append(post)
} else {
print("Zhenya: failed to convert")
}
}
}
})

Let's take a look at what's happening here.

The first line is observing posts/$post-id in Firebase using the .value method. The .value method returns the reference you provide in a FIRDataSnapshot, where snapshot.key is the name of the child and snapshot.value is a dictionary containing its children.

So, snapshot in DataService.ds.REF_POSTS.child(key!).observe(.value, with: { (snapshot) in ... }) should look like this:

$post-id
imageUrl: http://...
likes: 0
userKey: ...

But on the very next line you redefine snapshot using a local definition. That is why you are getting the children of the post, because you explicitly define it that way:

if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] { ... }

If you remove this, you should see the behavior you want.


So what should you really be doing? Well I don't like using .value because it has a few drawbacks:

  • It downloads the entire node requested. If you mess up your reference for a large node (i.e, ref/users), it's easy for a .value operation to take several seconds before you see any data.

  • It is only called once. This is fine if your database is static, but whose database is static?

This is why I prefer using .childAdded for things like posts, messages, etc. It returns a single node at a time, which means you can populate a list progressively as new data becomes available. Using a few clever UI functions (such as tableView.insertRows(...) you can fairly smoothly display large lists of data without waiting for results. I believe it also supports paging, where you get them in batches of 10, for example. Here is a quick primer on how you can begin using .childAdded to make the most of large lists of data.

Firebase Retrieve Data - Could not cast value

I took your code and shrunk it down a bit for testing, and it's working. (note Firebase 2.x on OS X and Swift 3 but the code is similar)

Firebase structure:

  "what-am" : {
"results" : [ {
"code" : "738/B738",
"data" : "Boeing",
"engines" : "Rolls"
}, {
"code" : "727/B727",
"data" : "Boeing",
"engines" : "Pratt"
} ]
}

Here's the Planes struct

struct Planes {

var code:String!
var data: String!
var engines: String!

init(code: String, data: String, engines: String ) {

self.code = code
self.data = data
self.engines = engines
}

init(snapshot: FDataSnapshot) {

let snapshotValue = snapshot.value as! [String:AnyObject]

code = snapshotValue["code"] as! String
data = snapshotValue["data"] as! String
engines = snapshotValue["engines"] as! String
}
}

and then the code that reads in two planes, populates and array and then prints the array.

let ref = self.myRootRef.child(byAppendingPath: "what-am/results")!

ref.observe(.value, with: { snapshot in

if ( snapshot!.value is NSNull ) {
print("not found")
} else {

var newItems: [Planes] = []

for item in (snapshot?.children)! {
let planesItem = Planes(snapshot: item as! FDataSnapshot)
newItems.append(planesItem)
}

self.planes = newItems
print(self.planes)

}
})

and finally the output

[Swift_Firebase_Test.Planes(code: 738/B738, data: Boeing, engines: Rolls),
Swift_Firebase_Test.Planes(code: 727/B727, data: Boeing, engines: Pratt)]

Key and name are nil as I removed then from the Planes structure.

The line you asked about

let snapshotValue = snapshot.value as! [String:AnyObject]

is valid as the snapshot contains a series of key:value pairs so String:AnyObject works.

This line changed due to Swift 3

for item in (snapshot?.children)!

but other than that, the code works.

Try this to ensure you are reading the correct node. This reads the above structure and prints out each engine type. (tested and works)

 let ref = self.myRootRef.child(byAppendingPath: "what-am/results")!
ref.observe(.value, with: { snapshot in
if ( snapshot!.value is NSNull ) {
print("not found")
} else {
for child in (snapshot?.children)! {
let snap = child as! FDataSnapshot
let dict = snap.value as! [String: String]
let engines = dict["engines"]
print(engines!)
}
}
})

How to associate to different data objects in Firebase 3 with each other using Swift?

It would be better if you could structure your data in Firebase. Here is the links that provide nice intuition about structuring data in Firebase:

https://firebase.googleblog.com/2013/04/denormalizing-your-data-is-normal.html
https://www.firebase.com/docs/web/guide/structuring-data.html

You can define your user table and posts as follow:

user{
"u1" {

userName : "abc",
posts {
p1 : true,
p2 : true
}
},

"u2" {

userName : "def",
posts {
p3 : true,
p4 : true
}
}

}

post{
"p1"{
userId : "u1",
postComment : "hello ios",
postDate : "1467570919"
},
"p2"{
userId : "u1",
postComment : "ios",
postDate : "1467570920"
},
"p3"{
userId : "u2",
postComment : "hello ios",
postDate : "1467570921"
},
"p4"{
userId : "u2",
postComment : "hello ios",
postDate : "1467570922"
}
}

Also you can creates your entities as follow:

class UserEntity{

var userID: String
var userName : String
var posts: Dictionary<String, Bool>?
var ref : FIRDatabaseReference?

init(userID: String, userName: String, posts: Dictionary<String, Bool>?){
self.userID = userID
self.userName = userName
self.posts = posts
self.ref = nil
}
}


class PostEntity{

var pId: String
var uId: String
var postedComment: String
var postDate: NSTimeInterval
var ref : FIRDatabaseReference?

init(pId: String, uId: String, postedComment: String, postDate: NSTimeInterval){
self.pId= pId
self.uId = uId
self.postedComment = postedComment
self.postDate = postDate
self.ref = nil
}
}

Also you would want to structure your UserEntity and PostEntity entity as answered in this post.

You have to update the posts attribute of user table as p5 :true, when a new post p5 is added by user u1.

Firebase 2.0 - Reading data from FIRDataSnapshot not working

Your snapshot contains the value "12345UIDEXample" on the first level of children.

To access the data you are looking for you can use a loop through the children casting as a FIRDataSnapshot.

for child in snapshot.children.allObjects as! [FIRDataSnapshot]{
let firstname = child.value!["first_name"] as? String
}

In this example child value will only return another snapshot of your object like so:

Snap(12345UIDEXample) {
"first_name" = Bob;
"last_name" = Someone;
"profile_picture_url" = "exampleurl.com";
username = bobby;
};

However you will be able to access the desired fields as you tried to previously.



Related Topics



Leave a reply



Submit