8

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

例如:

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

这里的键dict都是 type String,但是值的类型不同。

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

我想知道是否有一种方法可以JSONEncoder在 Swift 4 中使用将其编码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协议的情况下完成它。

4

2 回答 2

14

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

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 的评论考虑定制策略

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)
    }
}
于 2018-07-20T17:44:16.827 回答
2

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

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

这个链接也会有所帮助。

这就是我使用它的方式:

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为每种情况制作和编写编码代码。

于 2018-07-20T19:08:12.063 回答