在Swift 4中使用JSONEncoder将[String:Encodable]字典编码为JSON

时间:2018-07-20 16:58:15

标签: dictionary swift4 jsonencoder encodable

我很好奇如何使用String键和Encodable值将字典编码为JSON。

例如:

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

dict中的键都是String类型,但是值的类型各不相同。

但是,所有这些类型都可以在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协议来完成此操作。

2 个答案:

答案 0 :(得分:4)

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

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)

结果如下:

  

{     “ Double”:3.1400000000000001,     “ String”:“ test”,     “布尔”:否,     “整数”: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     “ Double”:3.1400000000000001,     “布尔”:否,     “ String”:“测试”   }

编辑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)
    }
}

答案 1 :(得分:1)

由于使用Encodable协议,因此您需要一个包装器,以便知道哪个项目可以更容易地进行编码。

我建议使用一个名为JSONValue的枚举,该枚举对所有IntStringDoubleArray,{{1} }案件。那么您可以使用类型安全的方式编写JSON。

link也会有所帮助。

这是我的用法:

Dictionary

然后制作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) } 并为每种情况编写编码代码。