Can Swift 4's JSONDecoder be used with Firebase Realtime Database?

£可爱£侵袭症+ 提交于 2020-12-24 06:48:24

问题


I am trying to decode data from a Firebase DataSnapshot so that it can be decoded using JSONDecoder.

I can decode this data fine when I use a URL to access it with a network request (obtaining a Data object).

However, I want to use the Firebase API to directly obtain the data, using observeSingleEvent as described on this page.

But, when I do this, I cannot seem to convert the result into a Data object, which I need to use JSONDecoder.

Is it possible to do the new style of JSON decoding with a DataSnapshot? How is it possible? I can't seem to figure it out.


回答1:


I have created a library called CodableFirebase that provides Encoders and Decoders that are designed specifically for Firebase.

So for the example above:

import Firebase
import CodableFirebase

let item: GroceryItem = // here you will create an instance of GroceryItem
let data = try! FirebaseEncoder().encode(item)

Database.database().reference().child("pathToGraceryItem").setValue(data)

And here's how you will read the same data:

Database.database().reference().child("pathToGraceryItem").observeSingleEvent(of: .value, with: { (snapshot) in
    guard let value = snapshot.value else { return }
    do {
        let item = try FirebaseDecoder().decode(GroceryItem.self, from: value)
        print(item)
    } catch let error {
        print(error)
    }
})



回答2:


I've converted Firebase Snapshots using JSONDecoder by converting snapshots back to JSON in Data format. Your struct needs to conform to Decodable or Codable. I've done this with SwiftyJSON but this example is using JSONSerialization and it still works.

JSONSnapshotPotatoes {
    "name": "Potatoes",
    "price": 5,
}
JSONSnapshotChicken {
    "name": "Chicken",
    "price": 10,
    "onSale": true
}

struct GroceryItem: Decodable {
    var name: String
    var price: Double
    var onSale: Bool? //Use optionals for keys that may or may not exist
}


Database.database().reference().child("grocery_item").observeSingleEvent(of: .value, with: { (snapshot) in
        guard let value = snapshot.value as? [String: Any] else { return }
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
            let groceryItem = try JSONDecoder().decode(GroceryItem.self, from: jsonData)

            print(groceryItem)
        } catch let error {
            print(error)
        }
    })

Please note that if your JSON keys are not the same as your Decodable struct. You'll need to use CodingKeys. Example:

JSONSnapshotSpinach {
    "title": "Spinach",
    "price": 10,
    "onSale": true
}

struct GroceryItem: Decodable {
    var name: String
    var price: Double
    var onSale: Bool?

    enum CodingKeys: String, CodingKey {
        case name = "title"

        case price
        case onSale
    }
}

You can find more information on this using Apple Docs here.




回答3:


No. Firebase returns a FIRDataSnapshot that can't be decodable. You can use this structure however, which is pretty simple and easy to understand:

struct GroceryItem {
  
  let key: String
  let name: String
  let addedByUser: String
  let ref: FIRDatabaseReference?
  var completed: Bool
  
  init(name: String, addedByUser: String, completed: Bool, key: String = "") {
    self.key = key
    self.name = name
    self.addedByUser = addedByUser
    self.completed = completed
    self.ref = nil
  }
  
  init(snapshot: FIRDataSnapshot) {
    key = snapshot.key
    let snapshotValue = snapshot.value as! [String: AnyObject]
    name = snapshotValue["name"] as! String
    addedByUser = snapshotValue["addedByUser"] as! String
    completed = snapshotValue["completed"] as! Bool
    ref = snapshot.ref
  }
  
  func toAnyObject() -> Any {
    return [
      "name": name,
      "addedByUser": addedByUser,
      "completed": completed
    ]
  }
  
}

And use toAnyObject() to save your item:

let groceryItemRef = ref.child("items")

groceryItemRef.setValue(groceryItem.toAnyObject())

Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2




回答4:


Or you can use this solution for children

extension DatabaseReference {
  func makeSimpleRequest<U: Decodable>(completion: @escaping (U) -> Void) {
    self.observeSingleEvent(of: .value, with: { snapshot in
        guard let object = snapshot.children.allObjects as? [DataSnapshot] else { return }
        let dict = object.compactMap { $0.value as? [String: Any] }
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
            let parsedObjects = try JSONDecoder().decode(U.self, from: jsonData)
            completion(parsedObjects)
        } catch let error {
            print(error)
        }
    })
  }
}

and use

self.refPriceStatistics.child(productId).makeSimpleRequest { (parsedArray: [YourArray]) in
    callback(parsedArray)
}



回答5:


You can convert the value returned by Firebase to Data, and then decode that.

Add this extension to your project:

extension Collection {
    //Designed for use with Dictionary and Array types
    var jsonData: Data? {
        return try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
    }
}

Then use it to convert the value of the observed snapshot into data, which can then be decoded:

yourRef.observe(.value) { (snapshot) in
    guard snapshot.exists(),
       let value = snapshot.value as? [String],
       let data = value.jsonData else { 
       return
    }
    //cast to expected type
    do {
        let yourNewObject =  try JSONDecoder().decode([YourClass].self, from: data)
    } catch let decodeError {
        print("decodable error")
    }
}



回答6:


If your data type is Codable you can use the following solution to decode directly. You do not need any plugin. I used the solution for Cloud Firestore.

import Firebase
import FirebaseFirestoreSwift


let db = Firestore.firestore()
let query = db.collection("CollectionName")
            .whereField("id", isEqualTo: "123")

guard let documents = snapshot?.documents, error == nil else {
    return
}

if let document = documents.first {
    do {
        let decodedData = try document.data(as: ModelClass.self) 
        // ModelClass a Codable Class

    }
    catch let error {
        // 
    }
}



回答7:


You can use this library CodableFirebase or the following extension can be helpful.

extension JSONDecoder {

func decode<T>(_ type: T.Type, from value: Any) throws -> T where T : Decodable {
    do {
        let data = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
        let decoded = try decode(type, from: data)
        return decoded

    } catch {
        throw error
    }
}


来源:https://stackoverflow.com/questions/47464436/can-swift-4s-jsondecoder-be-used-with-firebase-realtime-database

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!