How to decode a property with type of JSON dictionary in Swift [45] decodable protocol
With some inspiration from this gist I found, I wrote some extensions for UnkeyedDecodingContainer
and KeyedDecodingContainer
. You can find a link to my gist here. By using this code you can now decode any Array<Any>
or Dictionary<String, Any>
with the familiar syntax:
let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)
or
let array: [Any] = try container.decode([Any].self, forKey: key)
Edit: there is one caveat I have found which is decoding an array of dictionaries [[String: Any]]
The required syntax is as follows. You'll likely want to throw an error instead of force casting:
let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]
EDIT 2: If you simply want to convert an entire file to a dictionary, you are better off sticking with api from JSONSerialization as I have not figured out a way to extend JSONDecoder itself to directly decode a dictionary.
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { // appropriate error handling return}
The extensions
// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411astruct JSONCodingKeys: CodingKey { var stringValue: String init?(stringValue: String) { self.stringValue = stringValue } var intValue: Int? init?(intValue: Int) { self.init(stringValue: "\(intValue)") self.intValue = intValue }}extension KeyedDecodingContainer { func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> { let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) return try container.decode(type) } func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? { guard contains(key) else { return nil } guard try decodeNil(forKey: key) == false else { return nil } return try decode(type, forKey: key) } func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> { var container = try self.nestedUnkeyedContainer(forKey: key) return try container.decode(type) } func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? { guard contains(key) else { return nil } guard try decodeNil(forKey: key) == false else { return nil } return try decode(type, forKey: key) } func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> { var dictionary = Dictionary<String, Any>() for key in allKeys { if let boolValue = try? decode(Bool.self, forKey: key) { dictionary[key.stringValue] = boolValue } else if let stringValue = try? decode(String.self, forKey: key) { dictionary[key.stringValue] = stringValue } else if let intValue = try? decode(Int.self, forKey: key) { dictionary[key.stringValue] = intValue } else if let doubleValue = try? decode(Double.self, forKey: key) { dictionary[key.stringValue] = doubleValue } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) { dictionary[key.stringValue] = nestedDictionary } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) { dictionary[key.stringValue] = nestedArray } } return dictionary }}extension UnkeyedDecodingContainer { mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> { var array: [Any] = [] while isAtEnd == false { // See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays. if try decodeNil() { continue } else if let value = try? decode(Bool.self) { array.append(value) } else if let value = try? decode(Double.self) { array.append(value) } else if let value = try? decode(String.self) { array.append(value) } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) { array.append(nestedDictionary) } else if let nestedArray = try? decode(Array<Any>.self) { array.append(nestedArray) } } return array } mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> { let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self) return try nestedContainer.decode(type) }}
I have played with this problem, too, and finally wrote a simple library for working with “generic JSON” types. (Where “generic” means “with no structure known in advance”.) Main point is representing the generic JSON with a concrete type:
public enum JSON { case string(String) case number(Float) case object([String:JSON]) case array([JSON]) case bool(Bool) case null}
This type can then implement Codable
and Equatable
.
You can create metadata struct which confirms to Decodable
protocol and use JSONDecoder
class to create object from data by using decode method like below
let json: [String: Any] = [ "object": "customer", "id": "4yq6txdpfadhbaqnwp3", "email": "john.doe@example.com", "metadata": [ "link_id": "linked-id", "buy_count": 4 ]]struct Customer: Decodable { let object: String let id: String let email: String let metadata: Metadata}struct Metadata: Decodable { let link_id: String let buy_count: Int}let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)let decoder = JSONDecoder()do { let customer = try decoder.decode(Customer.self, from: data) print(customer)} catch { print(error.localizedDescription)}