Reputation: 6186
Primarily my use case is to create an object using a dictionary: e.g.
struct Person: Codable { let name: String }
let dictionary = ["name": "Bob"]
let person = Person(from: dictionary)
I would like to avoid writing custom implementations and want to be as efficient as possible.
Upvotes: 22
Views: 17206
Reputation: 25294
based on Chris Mitchelmore answer
import Foundation
enum DictionaryParsingError: Error {
case jsonSerialization(Error)
case decode(Error)
}
extension Decodable {
static func from<Key>(dictionary: [Key: Any],
options: JSONSerialization.WritingOptions = [],
decoder: JSONDecoder) -> Result<Self, DictionaryParsingError> where Key: Hashable {
let data: Data
do {
data = try JSONSerialization.data(withJSONObject: dictionary, options: options)
} catch let error {
return .failure(.jsonSerialization(error))
}
do {
return .success(try decoder.decode(Self.self, from: data))
} catch let error {
return .failure(.decode(error))
}
}
static func from<Key>(dictionary: [Key: Any],
options: JSONSerialization.WritingOptions = [],
singleUseDecoder configuration: (JSONDecoder) -> ()) -> Result<Self, DictionaryParsingError> where Key: Hashable {
let decoder = JSONDecoder()
configuration(decoder)
return from(dictionary: dictionary, options: options, decoder: decoder)
}
}
struct Item: Decodable {
let id: Int
let name: String
var date: Date
let isActive: Bool
}
let dictionary = ["id": 1,
"name": "Item",
"date": "2019-08-06T06:55:00.000-04:00",
"is_active": false] as [String : Any]
print("========================")
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
switch Item.from(dictionary: dictionary, decoder: decoder) {
case let .failure(error): print("ERROR: \(error)")
case let .success(item): print(item)
}
print("\n========================")
let item2 = Item.from(dictionary: dictionary) { decoder in
decoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
}
switch item2 {
case let .failure(error): print("ERROR: \(error)")
case let .success(item): print(item)
}
print("\n========================")
let item3 = Item.from(dictionary: [String:Any]()) { decoder in
decoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
}
switch item3 {
case let .failure(error): print("ERROR: \(error)")
case let .success(item): print(item)
}
========================
Item(id: 1, name: "Item", date: 2019-08-06 10:55:00 +0000, isActive: false)
========================
Item(id: 1, name: "Item", date: 2019-08-06 10:55:00 +0000, isActive: false)
========================
ERROR: decode(Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil)))
Upvotes: 4
Reputation: 3313
I adapted Chris Mitchelmore's answer so that it is a failable initializer instead of throwing code. Makes it a bit handier in some cases.
extension Decodable {
init?(from: Any) {
guard let data = try? JSONSerialization.data(withJSONObject: from, options: .prettyPrinted) else { return nil }
let decoder = JSONDecoder()
guard let decoded = try? decoder.decode(Self.self, from: data) else { return nil }
self = decoded
}
}
Upvotes: 2
Reputation: 6186
At the moment the best solution I have is this but it has the overhead of encoding/decoding.
extension Decodable {
init(from: Any) throws {
let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: data)
}
}
Following from the example in the question the result would be
let person = Person(from: dictionary)
If you're interested in going the other way then this might help https://stackoverflow.com/a/46329055/1453346
Upvotes: 56