Swift 4的JSONDecoder能够用于Firebase实时数据库吗?

17

我正在尝试解码来自Firebase DataSnapshot的数据,以便可以使用JSONDecoder进行解码。

当我使用URL进行网络请求(获取Data对象)访问时,我可以很好地解码此数据。

然而,我想使用Firebase API直接获取数据,使用observeSingleEvent如此页面所述

但是,当我这样做时,无法将结果转换为Data对象,我需要使用JSONDecoder。

是否可以使用DataSnapshot进行新样式的JSON解码? 如何做到呢? 我好像无法弄清楚。


当您从Firebase获取数据时,不应使用JSONDecoder。相反,您应该使用Firebase API。 - Tigran Iskandaryan
7个回答

31

我创建了一个名为CodableFirebase的库,它提供了专门针对Firebase设计的EncodersDecoders

因此,对于上面的示例:

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)

以下是如何读取相同数据的方法:

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)
    }
})

17

我使用JSONDecoder将Firebase快照转换为Data格式的JSON,您的结构体需要符合Decodable或Codable协议。我已经使用SwiftyJSON完成了这个过程,但这个示例使用JSONSerialization,它仍然可以工作。

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)
        }
    })
请注意,如果您的JSON键与可解码结构体不同,您需要使用CodingKeys。例如:
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
    }
}

您可以使用Apple文档此处获取有关此信息的更多信息。


这可能是最好的解决方案,而不需要使用任何第三方库。真棒发现! - Cyril

6

不,Firebase返回的是无法解码的FIRDataSnapshot。但是你可以使用这个结构,它非常简单易懂:

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
    ]
  }
  
}

使用 toAnyObject() 方法保存您的项目:

let groceryItemRef = ref.child("items")

groceryItemRef.setValue(groceryItem.toAnyObject())

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

本文将介绍如何使用Firebase,这是一个由Google提供的云端后端服务。Firebase提供多种功能,包括实时数据库、身份验证和云存储等等。使用Firebase可以极大地简化应用程序的后端开发工作,并且它非常易于集成到现有的应用程序中。

在这个教程中,我们将学习如何设置Firebase项目,添加Firebase到我们的应用程序中,以及如何实现Firebase实时数据库。


1
太棒了。这正是我所期望的方向。我没有意识到你可以直接以这种方式使用快照。这很容易修改,因为我已经有了可解码的数据结构。而且,结果证明,这段代码甚至比原始请求代码还要小!双赢。 - wazawoo
@ricardo如果节点也有子节点,那么toAnyObject() -> Any函数需要如何更改呢? - Learn2Code
@Learn2Code,你还需要在这些子元素中添加“toAnyObject() -> Any”函数,就像这样: ‘object: yourObject.toAnyObject,’ - Ricardo Daniel
@RicardoDaniel你能否请看一下以下问题 https://stackoverflow.com/questions/63911991/issue-reading-firebase-realtime-database-parent-node-with-children-nodes-in-swif 。根据讨论,似乎我不能像您的答案描述的那样轻松地完成。 - Learn2Code

4
或者您可以使用这个方案来为孩子们解决问题。
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)
        }
    })
  }
}

并使用

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

3
如果你的数据类型是Codable,可以使用以下解决方案直接解码。无需任何插件。我在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 {
        // 
    }
}

2
你可以将Firebase返回的值转换为Data,然后对其进行解码。
将此扩展添加到您的项目中:
extension Collection {
    //Designed for use with Dictionary and Array types
    var jsonData: Data? {
        return try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
    }
}

然后使用它将观察快照的值转换为数据,然后可以对其进行解码。
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")
    }
}

0
你可以使用这个库 CodableFirebase 或者下面的扩展也会很有帮助。
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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接