[swift-users] Decode a JSON object of unknown format into a Dictionary with Decodable in Swift 4
Jon Shier
jon at jonshier.com
Fri Jun 23 11:27:27 CDT 2017
Your gist is extremely interesting to me. I had tried something similar with Argo’s existing JSON enum, but was somewhat stymied by trying to decode it from an unkeyed container. I suppose I still don’t have a good grasp on all of the existing container types.
Jon
> On Jun 23, 2017, at 11:46 AM, Randy Eckenrode via swift-users <swift-users at swift.org> wrote:
>
>>
>> On Jun 17, 2017, at 10:07 PM, Chris Anderson via swift-users <swift-users at swift.org <mailto:swift-users at swift.org>> wrote:
>>
>> Say I have a JSON object such as:
>>
>> {
>> "id": "4yq6txdpfadhbaqnwp3",
>> "email": "john.doe at example.com <mailto:john.doe at 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 <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 at example.com <mailto:john.doe at 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 at example.com <mailto:john.doe at 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 at example.com <mailto:john.doe at example.com>","id":"4yq6txdpfadhbaqnwp3","metadata":{"link_id":"linked-id","buy_count":4},"name":"John Doe"}
>
> --
> Randy Eckenrode
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org <mailto:swift-users at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-users <https://lists.swift.org/mailman/listinfo/swift-users>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170623/a5cf91ae/attachment.html>
More information about the swift-users
mailing list