-
-
Save GeekAndDad/c3b3fb030595b18fe153d526a815128e to your computer and use it in GitHub Desktop.
import Foundation | |
// based on clever example from Nick Lockwood: https://gist.github.com/nicklockwood/833fabacbc4b2d11ae7c7d4752b8fd18 | |
// I only needed Encodable so trimmed it down to just that. | |
protocol AnyEncodableValue: Encodable {} | |
extension AnyEncodableValue { | |
func encode(to container: inout SingleValueEncodingContainer) throws { | |
try container.encode(self) | |
} | |
} | |
struct AnyEncodable: Encodable { | |
let value: AnyEncodableValue | |
init(_ value: AnyEncodableValue) { | |
self.value = value | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try value.encode(to: &container) | |
} | |
} | |
// Test it out: | |
struct Bar: AnyEncodableValue { | |
var bar: Int | |
} | |
struct Baz: AnyEncodableValue { | |
var baz: String | |
} | |
let coder = JSONEncoder() | |
coder.outputFormatting = .prettyPrinted | |
let values: [AnyEncodableValue] = [ | |
Bar(bar: 5), | |
Baz(baz: "Hello"), | |
] | |
let typeErasedValues = values.map(AnyEncodable.init) | |
let data = try coder.encode(typeErasedValues) | |
print(String(data: data, encoding: .utf8)!) | |
// try as function argument: | |
func test(value: [AnyEncodable]) throws -> Data { | |
let coder = JSONEncoder() | |
coder.outputFormatting = .prettyPrinted | |
let data = try coder.encode(value) | |
return data | |
} | |
let dd = try test(value: typeErasedValues) | |
print(String(data: dd, encoding: .utf8)!) | |
// try single value | |
let typeErasedValue = AnyEncodable(values[0]) | |
func test(value: AnyEncodable) throws -> Data { | |
let coder = JSONEncoder() | |
coder.outputFormatting = .prettyPrinted | |
let data = try coder.encode(value) | |
return data | |
} | |
let d = try test(value: typeErasedValue) | |
print(String(data: d, encoding: .utf8)!) | |
// Single value without the pre-type erasing step required (nice!): | |
func test(value: AnyEncodableValue) throws -> Data { | |
let typeErasedValue = AnyEncodable(value) | |
let coder = JSONEncoder() | |
coder.outputFormatting = .prettyPrinted | |
let data = try coder.encode(typeErasedValue) | |
return data | |
} | |
let d3 = try test(value: values[0]) | |
print(String(data: d3, encoding: .utf8)!) |
Proposed simplification:
import Foundation
// Private, since it's only used internally by AnyEncodable and we don't want to pollute a public protocol
private extension Encodable {
func encode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyEncodable: Encodable {
let value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try value.encode(to: &container)
}
}
// Test it out:
struct Bar: Encodable {
var bar: Int
}
struct Baz: Encodable {
var baz: String
}
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
let values: [Encodable] = [
Bar(bar: 5),
Baz(baz: "Hello"),
]
let typeErasedValues = values.map(AnyEncodable.init)
let data = try coder.encode(typeErasedValues)
print(String(data: data, encoding: .utf8)!)
// try as function argument:
func test(value: [AnyEncodable]) throws -> Data {
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
let data = try coder.encode(value)
return data
}
let dd = try test(value: typeErasedValues)
print(String(data: dd, encoding: .utf8)!)
// try single value
let typeErasedValue = AnyEncodable(values[0])
func test(value: AnyEncodable) throws -> Data {
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
let data = try coder.encode(value)
return data
}
let d = try test(value: typeErasedValue)
print(String(data: d, encoding: .utf8)!)
// Single value without the pre-type erasing step required (nice!):
func test(value: Encodable) throws -> Data {
let typeErasedValue = AnyEncodable(value)
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
let data = try coder.encode(typeErasedValue)
return data
}
let d3 = try test(value: values[0])
print(String(data: d3, encoding: .utf8)!)
Nice! I don't think about extending existing built-in types enough yet. And making that extension private to avoid polluting namespace is a nice tip. (What happens if someone else declares the same function in a public or private extension? Hmm. Now I have to go test that :)
What happens if someone else declares the same function in a public or private extension?
As long as it's not annotated with @objc
it's fine for several different files to define the same private extension function, even within the same module. Such is the joy of Swift's proper access levels and name-spacing (as opposed to C-style privacy-by-obscurity).
Added test of normal Encodable types which I didn't do properly late last night:
// Single value without the pre-type erasing step required (nice!):
func testV(value: Encodable) throws -> Data {
let typeErasedValue = AnyEncodable(value)
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
coder.dateEncodingStrategy = .iso8601
let data = try coder.encode(typeErasedValue)
return data
}
print("\ntestV\n")
let s = "Hello!"
let now = Date()
let d3 = try testV(value: s)
print(String(data: d3, encoding: .utf8)!)
let d4 = try testV(value: now)
print(String(data: d4, encoding: .utf8)!)
output:
testV
"Hello!"
"2019-10-28T03:11:11Z"
Ah, this made me realize that this doesn't work for my case. I need keyed encoding which requires the generic which causes the problem. doh!
I need keyed encoding which requires the generic which causes the problem. doh!
I'm not sure what you mean by this. String and Date are encoded with single-value containers by default, but a more complex object will be encoded as a dictionary using keyed encoding. The fact we use a singleValueContainer
for the AnyEncodable
wrapper won't change that.
Oi! Thanks so much for the reply! Made me look again and realize my test code was flawed (grrr). Sure enough! As you said, it works great:
struct User: Encodable {
var name: String
var phone: String
var userID: String
var joinedDate: Date
}
let user = User(name: "Joe Bob", phone: "15551212", userID: "201910280001", joinedDate: Date())
let typeErasedValue = AnyEncodable(user)
func test(value: AnyEncodable) throws -> Data {
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
coder.dateEncodingStrategy = .iso8601
let data = try coder.encode(value)
return data
}
let d = try test(value: typeErasedValue)
print(String(data: d, encoding: .utf8)!)
// Try single value without the pre-type erasing step required:
func testV(value: Encodable) throws -> Data {
let typeErasedValue = AnyEncodable(value)
let coder = JSONEncoder()
coder.outputFormatting = .prettyPrinted
coder.dateEncodingStrategy = .iso8601
let data = try coder.encode(typeErasedValue)
return data
}
print("\ntestV\n")
let d2 = try testV(value: user)
print(String(data: d2, encoding: .utf8)!)
produces:
{
"phone" : "15551212",
"userID" : "201910280001",
"name" : "Joe Bob",
"joinedDate" : "2019-10-28T22:33:13Z"
}
testV
{
"phone" : "15551212",
"userID" : "201910280001",
"name" : "Joe Bob",
"joinedDate" : "2019-10-28T22:33:13Z"
}
Bingo! 💥
output: