将编码器存储在 Swift 字典中

Store Encodables in a Swift Dictionary

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

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

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

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

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

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

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

var dictionary: [String: Serializable<X>]

这里 'X' 应该是什么,或者,实现它的正确方法是什么? 我错过了什么?

两种可能的方法:

  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

这是我的解决方案(由 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)
}