【问题标题】:Swift converting Decodable struct to DictionarySwift 将 Decodable 结构转换为 Dictionary
【发布时间】:2021-01-13 16:39:14
【问题描述】:

我创建了一个 decoadable 结构,用于解析存储在 firebase 中的数据。

struct DiscussionMessage: Decodable {
    var message: String
    var userCountryCode: String
    var userCountryEmoji: String
    var messageTimestamp: Double
    var userName: String
    var userEmailAddress: String
    var fcmToken: String?
    var question: String?
    var recordingUrl: String?
}

我想使用这个结构来在 firebase 中存储数据。但我得到了错误:

*** 由于未捕获的异常“InvalidFirebaseData”而终止应用程序,原因:“(setValue:) 无法将 __SwiftValue 类型的对象存储在。只能存储 NSNumber、NSString、NSDictionary 和 NSArray 类型的对象。 以 NSException 类型的未捕获异常终止

当我这样存储数据时:

let message = DiscussionMessage(message: messageTextView.text, userCountryCode: userCountryCode, userCountryEmoji: userCountryEmoji, messageTimestamp: timestamp, userName: userName, userEmailAddress: userEmail, fcmToken: nil, question: nil, recordingUrl: nil)       
        
messagesReference.childByAutoId().setValue(message)

有没有办法将可解码对象转换为字典,以便我可以将其存储在 firebase 中?

【问题讨论】:

  • 你不能在 Firebase 实时数据库中存储 nil;没有值的节点不存在。我建议将结构更改为一个类,添加一个返回值的函数以写入字典 - 如果您确实需要节点存在,这也可以让您在 nil 的情况下存储默认值。
  • 我添加了一个可能比方法更短的答案。我们在当前项目中成功使用它,但请告诉我它是否有效。
  • 非常感谢!但我目前正在使用 Leo 的答案。它似乎更健壮。

标签: swift firebase firebase-realtime-database struct decodable


【解决方案1】:

你不需要它是可解码的。您需要的是能够对其进行编码(可编码)。因此,首先将您的结构声明为 Codable。编码后,您可以使用 JSONSerialization jsonObject 方法将数据转换为字典:

extension Encodable {
    func data(using encoder: JSONEncoder = .init()) throws -> Data { try encoder.encode(self) }
    func string(using encoder: JSONEncoder = .init()) throws -> String { try data(using: encoder).string! }
    func dictionary(using encoder: JSONEncoder = .init(), options: JSONSerialization.ReadingOptions = []) throws -> [String: Any] {
        try JSONSerialization.jsonObject(with: try data(using: encoder), options: options) as? [String: Any] ?? [:]
    }
}

extension Data {
    func decodedObject<D: Decodable>(using decoder: JSONDecoder = .init()) throws -> D {
        try decoder.decode(D.self, from: self)
    }
}

extension Sequence where Element == UInt8 {
    var string: String? { String(bytes: self, encoding: .utf8) }
}

我还将 srtuct 属性声明为常量。如果您需要更改任何值,只需创建一个新对象:

struct DiscussionMessage: Codable {
    let message, userCountryCode, userCountryEmoji, userName, userEmailAddress: String
    let messageTimestamp: Double
    let fcmToken, question, recordingUrl: String?
}

let message: DiscussionMessage = .init(message: "message", userCountryCode: "BRA", userCountryEmoji: "??", userName: "userName", userEmailAddress: "email@address.com", messageTimestamp: 1610557474.227274, fcmToken: "fcmToken", question: "question", recordingUrl: nil)

do {
    let string = try message.string()
    print(string)      // {"fcmToken":"fcmToken","userName":"userName","message":"message","userCountryEmoji":"??","userEmailAddress":"email@address.com","question":"question","messageTimestamp":1610557474.2272739,"userCountryCode":"BRA"}
    
    let dictionary = try message.dictionary()
    print(dictionary)  // ["userName": userName, "userEmailAddress": email@address.com, "userCountryEmoji": ??, "messageTimestamp": 1610557474.227274, "question": question, "message": message, "fcmToken": fcmToken, "userCountryCode": BRA]
    
    let data = try message.data()      // 218 bytes
    let decodedMessages: DiscussionMessage = try data.decodedObject()
    print("decodedMessages", decodedMessages)    // ecodedMessages DiscussionMessage(message: "message", userCountryCode: "BRA", userCountryEmoji: "??", userName: "userName", userEmailAddress: "email@address.com", messageTimestamp: 1610557474.227274, fcmToken: Optional("fcmToken"), question: Optional("question"), recordingUrl: nil)
} catch {
    print(error)
}

【讨论】:

  • 网上的答案都是用decodedable,然后用jsonEncoder从firebase决定snapshot.value。如果结构是 coadable 类型,我可以这样做吗?
  • Codable 只是一个类型别名 typealias Codable = Decodable &amp; Encodable
  • 哦,所以结构变得既可编码又可解码?
  • 是的。顺便说一句,您可以将您的 messageTimestamp 属性声明为 Date 并将 dateEncodingStrategy/dateDecodingStrategy 更改为自 1970 年以来的 timeInterval 或您需要的任何内容
  • nvm 添加所有扩展后错误消失
【解决方案2】:

我会抛出一个答案,看看是否有帮助。

我建议扩展 Encodable 以允许任何符合条件的对象返回可以写入 Firebase 的自身字典。

这是扩展名

extension Encodable {
    var dict: [String: Any]? {
        guard let data = try? JSONEncoder().encode(self) else { return nil }
        return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
    }
}

然后只是改变结构

struct DiscussionMessage: Encodable {
   ...your properties...
}

还有代码

let msg = DiscussionMessage(data to populate with)
let dict = msg.dict
messagesReference.childByAutoId().setValue(dict)

请记住,实时数据库没有 nil - 任何没有值的节点都不存在,因此只会写入具有值的属性。

哦 - 并确保属性是 NSNumber、NSString、NSDictionary 和 NSArray(和 Bool)的有效 Firebase 类型

【讨论】:

    猜你喜欢
    • 2017-11-19
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-15
    • 1970-01-01
    • 1970-01-01
    • 2017-01-15
    相关资源
    最近更新 更多