I am just curious how can I encode a dictionary with String
key and Encodable
value into JSON.
For example:
let dict: [String: Encodable] = [
"Int": 1,
"Double": 3.14,
"Bool": false,
"String": "test"
]
The keys in this dict
are all of type String
, but the type of the values vary.
However, all of these types are allowed in JSON.
I am wondering if there is a way to use JSONEncoder
in Swift 4 to encode this dict
into JSON Data
.
I do understand there are other ways without using JSONEncoder
to achieve this, but I am just wondering if JSONEncoder
is capable of managing this.
The Dictionary
do have a func encode(to encoder: Encoder) throws
in an extension, but that only applies for constraint Key: Encodable, Key: Hashable, Value: Encodable
, whereas for our dict
, it needs constraint Key: Encodable, Key: Hashable, Value == Encodable
.
Having a struct
for this will be sufficient to use JSONEncoder
,
struct Test: Encodable {
let int = 1
let double = 3.14
let bool = false
let string = "test"
}
However, I am interested to know if the it can be done without specifying the concrete type but just the Encodable
protocol.
Just figured out a way to achieve this with a wrapper:
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)
And here is the result:
{ "Double" : 3.1400000000000001, "String" : "test", "Bool" : false, "Int" : 1 }
I am still not happy with it. If there are any other approaches, I am more than happy to see it.
Thanks!
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)
Result:
{ "Int" : 1, "Double" : 3.1400000000000001, "Bool" : false, "String" : "test" }
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)
}
}
You would need a wrapper since with Encodable
protocol to know which item is which to be able to encode it easier.
I suggest Use an enum named JSONValue
which has 5 to 6 cases for all Int
, String
, Double
, Array
, Dictionary
cases. then you can write JSONs in a type-safe way.
This link will help too.
This is how I use it:
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)
}
And then make JSONValue: Encodable
and write encoding code for each case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With