在 iOS userInfo 上使用 Swift 的 Decodable

问题描述

iOS 从类型为 [AnyHashable : Any]后台通知中返回 json 数据。 有没有办法将其解析为实现 Codable 协议的结构?

示例:

// Server sends the following data via apn
{"items": [{"id": "192e7926-7891-44eb-8ca7-f795d8552e84","text": "some text","num": 0.7}]}

在eapp端接收数据时

func application(_ application: UIApplication,didReceiveRemoteNotification userInfo: [AnyHashable : Any],fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
   print(userInfo["items"])
}

print 语句给出以下输出

Optional(<__NSSingleObjectArrayI 0x283fe80b0>(
  {
    id = "192e7926-7891-44eb-8ca7-f795d8552e84";
    num = "0.7";
    text = "Check test";
  }
))

我已经有一个匹配的 Codable 结构:

struct Item: Codable {
    let id: UUID
    let text: String
    let num: Double
}

我可以以某种方式从 Item 实例化 userInfo["items"][0] 吗?显然,只需发送一个编码的 json 字符串就可以解决这个问题,但我很感兴趣,如果有办法将 [AnyHashable : Any] 解码为 Decodable 结构。

谢谢!

解决方法

解码一个已经被反序列化的对象不是很有效。

提取数据并手动创建结构实例

struct Item {
    let id: UUID
    let text: String
    let num: Double

    init?(dictionary: [String:Any]) {
       guard let id = dictionary["id"] as? String,let uuid = UUID(uuidString: id),let text = dictionary["text"] as? String,let num = dictionary["num"] as? Double else { return nil }
       self.id = uuid
       self.text = text
       self.num = num
    }
}

if let items = userInfo["items"] as? [[String:Any]],let firstItem = items.first,let item = Item(dictionary: firstItem) {
      print(item)
}
,

您可以使用 JSONSerialization.data(withJSONObject:

将 dict 转换为 JSON 对象

你可能需要一个容器结构

struct ItemsCollection: Decodable {
    let items: [Item]
}
        let dict: [AnyHashable: Any] = your dict
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: dict)
            let itemsCollection = try JSONDecoder().decode(ItemsCollection.self,from: jsonData)
            //now access it as itemsCollection.items[0]
        }
        catch {
            print(error)
        }

编辑 1:您始终可以通过避免使用

创建新结构来优化解决方案
        let dict: [AnyHashable: Any] = userInfo["items"][0] //not sure if you would need explicit type casting here,cant debug as I dont have actual object
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: dict)
            let item = try JSONDecoder().decode(Item.self,from: jsonData)
            //now access item
        }
        catch {
            print(error)
        }