【问题标题】:Encode a [String: Encodable] dictionary into JSON using JSONEncoder in Swift 4在 Swift 4 中使用 JSONEncoder 将 [String: E​​ncodable] 字典编码为 JSON
【发布时间】:2018-07-20 16:58:14
【问题描述】:

我只是好奇如何将带有String 键和Encodable 值的字典编码为 JSON。

例如:

let dict: [String: Encodable] = [
    "Int": 1,
    "Double": 3.14,
    "Bool": false,
    "String": "test"
]

这个dict中的key都是String类型,但是value的类型不同。

但是,JSON 中允许所有这些类型。

我想知道是否有办法在 Swift 4 中使用 JSONEncoder 将此 dict 编码为 JSON Data

我知道有其他方法可以不使用JSONEncoder 来实现这一点,但我只是想知道JSONEncoder 是否能够管理这一点。

Dictionary 在扩展中确实有一个func encode(to encoder: Encoder) throws,但这仅适用于约束Key: Encodable, Key: Hashable, Value: Encodable,而对于我们的dict,它需要约束Key: Encodable, Key: Hashable, Value == Encodable

为此拥有struct 就足以使用JSONEncoder

struct Test: Encodable {
    let int = 1
    let double = 3.14
    let bool = false
    let string = "test"
}

但是,我很想知道是否可以在不指定具体类型而只指定 Encodable 协议的情况下完成它。

【问题讨论】:

  • 你不能这样做,Codable 严重依赖具体类型。 encode( 是一个泛型方法,它需要一个符合 Encodable 而不是协议本身的 concrete 类型。

标签: dictionary swift4 jsonencoder encodable


【解决方案1】:

刚刚想出了一种使用包装器实现此目的的方法:

struct EncodableWrapper: Encodable {
    let wrapped: Encodable

    func encode(to encoder: Encoder) throws {
        try self.wrapped.encode(to: encoder)
    }
}

let dict: [String: Encodable] = [
    "Int": 1,
    "Double": 3.14,
    "Bool": false,
    "String": "test"
]
let wrappedDict = dict.mapValues(EncodableWrapper.init(wrapped:))
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(wrappedDict)
let json = String(decoding: jsonData, as: UTF8.self)
print(json)

结果如下:

{ “双”:3.1400000000000001, “字符串”:“测试”, “布尔”:假, “诠释”:1 }

我还是不满意。如果还有其他方法,我很高兴看到它。

谢谢!

编辑 1 将包装器移动到 JSONEncoder 的扩展中:

extension JSONEncoder {
    private struct EncodableWrapper: Encodable {
        let wrapped: Encodable

        func encode(to encoder: Encoder) throws {
            try self.wrapped.encode(to: encoder)
        }
    }
    func encode<Key: Encodable>(_ dictionary: [Key: Encodable]) throws -> Data {
        let wrappedDict = dictionary.mapValues(EncodableWrapper.init(wrapped:))
        return try self.encode(wrappedDict)
    }
}

let dict: [String: Encodable] = [
    "Int": 1,
    "Double": 3.14,
    "Bool": false,
    "String": "test"
]

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(dict)
let json = String(decoding: jsonData, as: UTF8.self)
print(json)

结果:

{ “诠释”:1, “双”:3.1400000000000001, “布尔”:假, “字符串”:“测试” }

编辑 2:根据 @Hamish 的 cmets 考虑自定义策略

private extension Encodable {
    func encode(to container: inout SingleValueEncodingContainer) throws {
        try container.encode(self)
    }
}

extension JSONEncoder {
    private struct EncodableWrapper: Encodable {
        let wrapped: Encodable

        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try self.wrapped.encode(to: &container)
        }
    }

    func encode<Key: Encodable>(_ dictionary: [Key: Encodable]) throws -> Data {
        let wrappedDict = dictionary.mapValues(EncodableWrapper.init(wrapped:))
        return try self.encode(wrappedDict)
    }
}

【讨论】:

  • 请注意,直接调用包装值的 encode(to:) 将绕过该类型的任何编码策略 - 请参阅 stackoverflow.com/a/48664310/2976878 了解尊重编码策略的包装器实现。
  • @Hamish 很好,我从没想过定制策略。顺便说一下,喜欢通用初始化方法。
  • @Hamish 我尝试使用您发布的链接,似乎singleValueContainer 仍然严重依赖泛型类型,并且它不会将协议类型作为输入。
  • 具有通用init&lt;Value : Encodable&gt;(_ value: Value)AnyEncodable 包装器确实不适用于任意Encodable 值——但是第一个包装器(具有init(_ value: Encodable))将使用这些值: )
【解决方案2】:

您需要一个包装器,因为使用Encodable 协议来知道哪个项目是哪个项目能够更容易地对其进行编码。

我建议使用一个名为 JSONValue 的枚举,它对所有 IntStringDoubleArrayDictionary 案例有 5 到 6 个案例。那么您可以以类型安全的方式编写 JSON。

这个link 也会有帮助。

我就是这样使用它的:

indirect enum JSONValue {
    case string(String)
    case int(Int)
    case double(Double)
    case bool(Bool)
    case object([String: JSONValue])
    case array([JSONValue])
    case encoded(Encodable)
}

然后制作JSONValue: Encodable并为每种情况编写编码代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多