> On Jun 17, 2017, at 10:07 PM, Chris Anderson via swift-users 
> <swift-users@swift.org> wrote:
> 
> Say I have a JSON object such as:
> 
>   {
>     "id": "4yq6txdpfadhbaqnwp3",
>     "email": "john....@example.com <mailto:john....@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....@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....@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....@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
https://lists.swift.org/mailman/listinfo/swift-users

Reply via email to