Skip to content

Instantly share code, notes, and snippets.

@KingOfBrian
Last active March 6, 2020 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KingOfBrian/8712d5f2ccd169dc042c6eed8c6f46d9 to your computer and use it in GitHub Desktop.
Save KingOfBrian/8712d5f2ccd169dc042c6eed8c6f46d9 to your computer and use it in GitHub Desktop.
Fix encoding of key wrapper types
/// Codable doesn't do a good job of Dictionaries with non-String keys.
/// This property wrapper fixes things.
///
/// Context: https://oleb.net/blog/2017/12/dictionary-codable-array/
/// Tests: StringKeyDictionaryTests
@propertyWrapper
public struct StringKeyDictionary<Key: RawRepresentable & Hashable & Decodable, Value: Codable>: Codable where Key.RawValue == String {
public var wrappedValue: [Key: Value]
public init(wrappedValue: [Key: Value]) {
self.wrappedValue = wrappedValue
}
public init(from decoder: Decoder) throws {
let value = try [String: Value](from: decoder)
let tuples = value.compactMap { (key, value) -> (Key, Value)? in
return Key(rawValue: key).flatMap { ($0, value) }
}
self.wrappedValue = Dictionary(tuples, uniquingKeysWith: { key, _ in key })
}
public func encode(to encoder: Encoder) throws {
let tuples = wrappedValue.map { (key, value) -> (String, Value) in
return (key.rawValue, value)
}
try Dictionary(tuples, uniquingKeysWith: { key, _ in key })
.encode(to: encoder)
}
}
class StringKeyDictionaryTests: XCTestCase {
private enum KeyType: String, Codable {
case test, keys
}
private struct BrokenKeyType: Codable {
let values: [KeyType: String]
}
private struct BrokenProof: Codable {
let values: [String]
}
private struct FixedKeyType: Codable {
@StringKeyDictionary
var values: [KeyType: String]
}
private struct FixedProof: Codable {
let values: [String: String]
}
func testBroken() throws {
let broken = BrokenKeyType(values: [
.test: "Test",
.keys: "Keys",
])
let data = try JSONEncoder().encode(broken)
let wat = try JSONDecoder().decode(BrokenProof.self, from: data)
XCTAssertEqual(wat.values.sorted(), ["test", "Test", "keys", "Keys"].sorted())
}
func testFixed() throws {
let broken = FixedKeyType(values: [
.test: "Test",
.keys: "Keys",
])
let data = try JSONEncoder().encode(broken)
let lols = try JSONDecoder().decode(FixedProof.self, from: data)
XCTAssertEqual(lols.values, ["test": "Test", "keys": "Keys"])
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment