如何在 Swift [45] 可解码协议中解码具有 JSON 字典类型的属性 [英] How to decode a property with type of JSON dictionary in Swift [45] decodable protocol

查看:43
本文介绍了如何在 Swift [45] 可解码协议中解码具有 JSON 字典类型的属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有 Customer 数据类型,它包含一个 metadata 属性,该属性可以包含客户对象中的任何 JSON 字典

struct Customer {让 ID:字符串让电子邮件:字符串让元数据:[字符串:任何]}

<小时>

<代码>{对象":客户","id": "4yq6txdpfadhbaqnwp3","email": "john.doe@example.com",元数据":{"link_id": "链接 ID",购买计数":4}}

metadata 属性可以是任意的 JSON 映射对象.

在我可以从 NSJSONDeserialization 的反序列化 JSON 转换属性之前,但使用新的 Swift 4 Decodable 协议,我仍然想不出办法做到这一点.

有谁知道如何在带有可解码协议的 Swift 4 中实现这一点?

解决方案

这个要点中得到一些启发

a> 我发现,我为UnkeyedDecodingContainerKeyedDecodingContainer 写了一些扩展.您可以在此处找到我的要点链接.通过使用此代码,您现在可以使用熟悉的语法解码任何 ArrayDictionary:

let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)

let array: [Any] = try container.decode([Any].self, forKey: key)

我发现有一个警告是解码一个字典数组 [[String: Any]] 所需的语法如下.您可能希望抛出错误而不是强制转换:

let items: [[String: Any]] = try container.decode(Array.self, forKey: .items) as![[字符串:任意]]

编辑 2:如果您只是想将整个文件转换为字典,最好坚持使用 JSONSerialization 中的 api,因为我还没有想出一种方法来扩展 JSONDecoder 本身以直接解码一本字典.

guard let json = try JSONSerialization.jsonObject(with: data, options: []) as?[字符串:任何]其他{//适当的错误处理返回}

扩展

//灵感来自 https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a结构 JSONCodingKeys: CodingKey {var stringValue: 字符串初始化?(字符串值:字符串){self.stringValue = stringValue}var intValue: 整数?初始化?(整数值:整数){self.init(stringValue: "(intValue)")self.intValue = intValue}}扩展 KeyedDecodingContainer {func decode(_ type: Dictionary.Type, forKey key: K) throws ->字典<字符串,任意>{让容器 = 尝试 self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)返回 try container.decode(type)}func decodeIfPresent(_ type: Dictionary.Type, forKey key: K) throws ->字典<字符串,任意>?{守卫包含(键)其他{返回零}守卫尝试 decodeNil(forKey: key) == false else {返回零}返回尝试解码(类型,forKey:key)}func decode(_ type: Array.Type, forKey key: K) throws ->数组<任何>{var 容器 = 尝试 self.nestedUnkeyedContainer(forKey: key)返回 try container.decode(type)}func decodeIfPresent(_ type: Array.Type, forKey key: K) throws ->数组<任何>?{守卫包含(键)其他{返回零}守卫尝试 decodeNil(forKey: key) == false else {返回零}返回尝试解码(类型,forKey:key)}func decode(_ type: Dictionary.Type) 抛出 ->字典<字符串,任意>{var dictionary = Dictionary()对于所有键中的键 {如果让 boolValue = 尝试?解码(Bool.self,forKey:key){字典[key.stringValue] = boolValue} else if let stringValue = try?解码(String.self,forKey:key){字典[key.stringValue] = stringValue否则如果让 intValue = 尝试?解码(Int.self,forKey:key){字典[key.stringValue] = intValue否则如果让 doubleValue = 尝试?解码(双.self,forKey:key){字典[key.stringValue] = doubleValue否则如果让nestedDictionary = 试试?decode(Dictionary.self, forKey: key) {字典[key.stringValue] = 嵌套字典否则如果让nestedArray = 尝试?解码(数组.self,forKey:key){字典 [key.stringValue] = 嵌套数组}}返回字典}}扩展 UnkeyedDecodingContainer {mutating func decode(_ type: Array.Type) throws ->数组<任何>{var 数组:[Any] = []而 isAtEnd == false {//首先查看 JSON 数组中的当前值是否为 `null` 并防止嵌套数组的无限递归.如果尝试 decodeNil() {继续} else if let value = try?解码(Bool.self){数组.附加(值)} else if let value = try?解码(双.self){数组.附加(值)} else if let value = try?解码(字符串.self){数组.附加(值)否则如果让nestedDictionary = 试试?解码(字典<字符串,任何>.self){array.append(nestedDictionary)否则如果让nestedArray = 尝试?解码(数组<任何>.self){array.append(nestedArray)}}返回数组}mutating func decode(_ type: Dictionary.Type) 抛出 ->字典<字符串,任意>{让nestedContainer = 尝试self.nestedContainer(keyedBy: JSONCodingKeys.self)返回尝试 nestedContainer.decode(type)}}

Let's say I have Customer data type which contains a metadata property that can contains any JSON dictionary in the customer object

struct Customer {
  let id: String
  let email: String
  let metadata: [String: Any]
}


{  
  "object": "customer",
  "id": "4yq6txdpfadhbaqnwp3",
  "email": "john.doe@example.com",
  "metadata": {
    "link_id": "linked-id",
    "buy_count": 4
  }
}

The metadata property can be any arbitrary JSON map object.

Before I can cast the property from a deserialized JSON from NSJSONDeserialization but with the new Swift 4 Decodable protocol, I still can't think of a way to do that.

Do anyone know how to achieve this in Swift 4 with Decodable protocol?

解决方案

With some inspiration from this gist I found, I wrote some extensions for UnkeyedDecodingContainer and KeyedDecodingContainer. You can find a link to my gist here. By using this code you can now decode any Array<Any> or Dictionary<String, Any> with the familiar syntax:

let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)

or

let array: [Any] = try container.decode([Any].self, forKey: key)

Edit: there is one caveat I have found which is decoding an array of dictionaries [[String: Any]] The required syntax is as follows. You'll likely want to throw an error instead of force casting:

let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]

EDIT 2: If you simply want to convert an entire file to a dictionary, you are better off sticking with api from JSONSerialization as I have not figured out a way to extend JSONDecoder itself to directly decode a dictionary.

guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
  // appropriate error handling
  return
}

The extensions

// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a

struct JSONCodingKeys: CodingKey {
    var stringValue: String

    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    var intValue: Int?

    init?(intValue: Int) {
        self.init(stringValue: "(intValue)")
        self.intValue = intValue
    }
}


extension KeyedDecodingContainer {

    func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> {
        let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
        guard contains(key) else { 
            return nil
        }
        guard try decodeNil(forKey: key) == false else { 
            return nil 
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> {
        var container = try self.nestedUnkeyedContainer(forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? {
        guard contains(key) else {
            return nil
        }
        guard try decodeNil(forKey: key) == false else { 
            return nil 
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
        var dictionary = Dictionary<String, Any>()

        for key in allKeys {
            if let boolValue = try? decode(Bool.self, forKey: key) {
                dictionary[key.stringValue] = boolValue
            } else if let stringValue = try? decode(String.self, forKey: key) {
                dictionary[key.stringValue] = stringValue
            } else if let intValue = try? decode(Int.self, forKey: key) {
                dictionary[key.stringValue] = intValue
            } else if let doubleValue = try? decode(Double.self, forKey: key) {
                dictionary[key.stringValue] = doubleValue
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedDictionary
            } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedArray
            }
        }
        return dictionary
    }
}

extension UnkeyedDecodingContainer {

    mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
        var array: [Any] = []
        while isAtEnd == false {
            // See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays.
            if try decodeNil() {
                continue
            } else if let value = try? decode(Bool.self) {
                array.append(value)
            } else if let value = try? decode(Double.self) {
                array.append(value)
            } else if let value = try? decode(String.self) {
                array.append(value)
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
                array.append(nestedDictionary)
            } else if let nestedArray = try? decode(Array<Any>.self) {
                array.append(nestedArray)
            }
        }
        return array
    }

    mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {

        let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self)
        return try nestedContainer.decode(type)
    }
}

这篇关于如何在 Swift [45] 可解码协议中解码具有 JSON 字典类型的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆