On Jun 23, 2017, at 11:46 AM, Randy Eckenrode via swift-users <swift-users@swift.org> wrote:
On Jun 17, 2017, at 10:07 PM, Chris Anderson via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Say I have a JSON object such as:
{
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe@example.com <mailto:john.doe@example.com>",
"name":"John Doe",
"metadata": {
"link_id": "linked-id",
"buy_count": 4
}
}
And with a struct of:
struct User: Codable {
var id: String
var email: String
var name: String
}
How can I decode the `metadata` field into a Dictionary?
I’ve tried doing things such as, in my struct,
var metadata: Dictionary
or
var metadata: [String: Any]
But I get the error
MyPlayground.playground:3:7: note: cannot automatically synthesize 'Encodable' because '<<error type>>' does not conform to 'Encodable'
var metadata: Dictionary
A meta or metadata field on many APIs (such as www.stripe.com <http://www.stripe.com/>\) can contain whatever you want, and I still want to be able to process it on the Swift end. How can I store that meta data field into a Dictionary that I can parse apart manually after?
It’s possible, but you have to do most of the work yourself because you the compiler can’t create implementations for you. See below for a possible implementation. Note that I just ignore types I don’t handle. I also took a stab at doing something general in this gist (https://gist.github.com/kenada/069e121371eb8db41231edfcd4bd14a8\), but it doesn’t implement very robust error handling or support encoding. It also doesn’t flatten down to Any/[Any]/[String: Any] (leaving it up to the user to destructure the enum).
import Foundation
struct User: Codable {
var id: String
var email: String
var name: String
var metadata: [String: Any]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StaticCodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.email = try container.decode(String.self, forKey: .email)
self.name = try container.decode(String.self, forKey: .name)
self.metadata = try User.decodeMetadata(from: container.superDecoder(forKey: .metadata))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StaticCodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.email, forKey: .email)
try container.encode(self.name, forKey: .name)
try encodeMetadata(to: container.superEncoder(forKey: .metadata))
}
static func decodeMetadata(from decoder: Decoder) throws -> [String: Any] {
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
var result: [String: Any] = [:]
for key in container.allKeys {
if let double = try? container.decode(Double.self, forKey: key) {
result[key.stringValue] = double
} else if let string = try? container.decode(String.self, forKey: key) {
result[key.stringValue] = string
}
}
return result
}
func encodeMetadata(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: DynamicCodingKeys.self)
for (key, value) in metadata {
switch value {
case let double as Double:
try container.encode(double, forKey: DynamicCodingKeys(stringValue: key)!)
case let string as String:
try container.encode(string, forKey: DynamicCodingKeys(stringValue: key)!)
default:
fatalError("unexpected type")
}
}
}
private enum StaticCodingKeys: String, CodingKey {
case id, email, name, metadata
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
self.init(stringValue: "")
self.intValue = intValue
}
}
}
let userJson = """
{
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe@example.com <mailto:john.doe@example.com>",
"name":"John Doe",
"metadata": {
"link_id": "linked-id",
"buy_count": 4
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let user = try! decoder.decode(User.self, from: userJson)
print(user)
// Prints: User(id: "4yq6txdpfadhbaqnwp3", email: "john.doe@example.com <mailto:john.doe@example.com>", name: "John Doe", metadata: ["buy_count": 4.0, "link_id": "linked-id"])
let encoder = JSONEncoder()
let data = try! encoder.encode(user)
print(String(data: data, encoding: .utf8)!)
// Prints: {"email":"john.doe@example.com <mailto:john.doe@example.com>","id":"4yq6txdpfadhbaqnwp3","metadata":{"link_id":"linked-id","buy_count":4},"name":"John Doe"}
--
Randy Eckenrode
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users