【问题标题】:Store Encodables in a Swift Dictionary在 Swift 字典中存储 Encodables
【发布时间】:2018-01-20 16:21:57
【问题描述】:

我希望将模型对象存储在 Dictionary 中,并希望使用 JSONEncoder 将整个字典序列化为数据,然后转换为字符串并保存。

这个想法是使用 Swift 4 的开箱即用 Encodable 来确保我添加到字典中的任何内容都将被序列化,其中可以包括原语和自定义对象(它们本身将符合 Encodable)。

挑战是我应该将字典声明为什么类型:

  • 如果我使用[String: Any],它将不知道如何编码Any,如果我必须将其转换为实际的具体类型,这有点违背泛型的目的
  • 如果我使用[String: Encodable],它会在运行时崩溃说 Encodable 不符合自身,这是可以理解的 需要具体类型

为了解决这个问题,我想到了创建一个包装器: 即具有关联类型的协议具有泛型类型值的结构:

struct Serializable<T: Encodable> {
    var value: T?

    init(value: T) {
       self.value = value
    }
}

但问题依然存在,在声明上述字典的类型时,我仍然必须提供具体类型..

var dictionary: [String: Serializable<X>]

“X”应该在这里,或者,实现这一目标的正确方法是什么? 我错过了什么?

【问题讨论】:

    标签: swift generics serialization encodable jsonencoder


    【解决方案1】:

    两种可能的方法:

    1. 您可以创建其值为 Encodable 包装类型的字典,该类型仅对底层值进行编码:

      struct EncodableValue: Encodable {
          let value: Encodable
      
          func encode(to encoder: Encoder) throws {
              try value.encode(to: encoder)
          }
      }
      

      那么你可以这样做:

      let dictionary = [
          "foo": EncodableValue(value: Foo(string: "Hello, world!")),
          "bar": EncodableValue(value: Bar(value: 42)),
          "baz": EncodableValue(value: "qux")
      ]
      
      let data = try! JSONEncoder().encode(dictionary)
      
    2. 您可以定义自己的Codable 类型而不是使用字典:

      struct RequestObject: Encodable {
          let foo: Foo
          let bar: Bar
          let baz: String
      }
      
      let requestObject = RequestObject(
          foo: Foo(string: "Hello, world!"), 
          bar: Bar(value: 42),
          baz: "qux"
      )
      
      let data = try! JSONEncoder().encode(requestObject)
      

    不用说,它们都假定FooBar 都符合Encodable

    【讨论】:

    • 我使用了第一种方法,因为它对我当前的代码的侵入性很小。有用!非常感谢。你能解释一下为什么在运行时 Swift 能够找出我们的“价值”是什么类型的 Encodable 吗?我之前尝试过类似的方法,但没有在 EncodableValue 结构中声明 func encode(to encoder: Encoder) throws,这不起作用。
    • 我注意到一件事,JSONEncoder 提供了一个dateEncodingStrategy,您可以在其中指定您希望如何显示您的可编码日期对象的格式。这在这里失败了,它回退到默认策略,它转换为自某个日期以来的 timeInterval 数。
    • @justintime - 关于你的第一个问题,EncodableValue 的底层方法是我们使用协议作为类型(参见The Swift Programming Language: Protocols as Types)。关于为什么你在encode(to:) 的尝试没有成功,我显然不能在没有看到你尝试的情况下发表评论。
    • 我提到的dateEncodingStrategy 问题;我已经复制并作为一个单独的问题提出here。如果你想看看。
    【解决方案2】:

    这是我的解决方案(由 Rob 回答改进):

    struct EncodableValue: Encodable {
        let value: Encodable
    
        func encode(to encoder: Encoder) throws {
            try value.encode(to: encoder)
        }
    }
    
    struct Storage: Encodable {
        var dict: [String: Encodable] = [:]
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            for (key, value) in dict {
                guard let codingKey = CodingKeys(stringValue: key) else {
                    continue
                }
                if let enc = value as? EncodableValue {
                    try container.encode(enc, forKey: codingKey)
                }
            }
        }
    
        struct CodingKeys: CodingKey {
            var stringValue: String
            init?(stringValue: String) {
                self.stringValue = stringValue
            }
            var intValue: Int?
            init?(intValue: Int) {
                return nil
            }
        }
    }
    
    let dict: [String: EncodableValue] = ["test": EncodableValue(value:1), "abc":EncodableValue(value:"GOGO")]
    let storage = Storage(dict: dict)
    
    do {
        let data = try JSONEncoder().encode(storage)
        let res = String(data: data, encoding: .utf8)
        print(res ?? "nil")
    } catch {
        print(error)
    }
    

    【讨论】:

    • 不完全明白,但似乎您正在使用动态密钥使存储在运行时可编码。有趣,将保存此以备后用。谢谢!
    • 维亚切斯拉夫,Storage 的目的是什么?你为什么不直接encodedict
    • @Rob,我认为你是对的。那是第一个实施。顺便说一句,我不会删除这个答案,因为 swift 4 JSON 动态解析没有好的例子。
    猜你喜欢
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多