Init an object conforming to Codable with a dictionary/array
Solution 1
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
Solution 2
based on Chris Mitchelmore answer
Details
- Xcode Version 10.3 (10G8), Swift 5
Solution
import Foundation
extension Decodable {
init(from value: Any,
options: JSONSerialization.WritingOptions = [],
decoder: JSONDecoder) throws {
let data = try JSONSerialization.data(withJSONObject: value, options: options)
self = try decoder.decode(Self.self, from: data)
}
init(from value: Any,
options: JSONSerialization.WritingOptions = [],
decoderSetupClosure: ((JSONDecoder) -> Void)? = nil) throws {
let decoder = JSONDecoder()
decoderSetupClosure?(decoder)
try self.init(from: value, options: options, decoder: decoder)
}
init?(discardingAnErrorFrom value: Any,
printError: Bool = false,
options: JSONSerialization.WritingOptions = [],
decoderSetupClosure: ((JSONDecoder) -> Void)? = nil) {
do {
try self.init(from: value, options: options, decoderSetupClosure: decoderSetupClosure)
} catch {
if printError { print("\(Self.self) decoding ERROR:\n\(error)") }
return nil
}
}
}
Usage
struct Item: Decodable {
let id: Int
let name: String
let isActive: Bool
var date: Date
}
let dictionary = ["id": 1, "name": "Item", "is_active": false,
"date": "2019-08-06T06:55:00.000-04:00"] as [String : Any]
do {
let item1 = try Item(from: dictionary) { decoder in
decoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
}
print(item1)
} catch {
print("Error: \(error)")
}
print("\n========================")
let item2 = Item(discardingAnErrorFrom: dictionary)
print(String(describing: item2))
print("\n========================")
let item3 = Item(discardingAnErrorFrom: dictionary, printError: true)
print(String(describing: item3))
print("\n========================")
let item4 = Item(discardingAnErrorFrom: dictionary){ decoder in
decoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
}
print(String(describing: item4))
Usage log
Item(id: 1, name: "Item", isActive: false, date: 2019-08-06 10:55:00 +0000)
========================
nil
========================
Item decoding ERROR:
keyNotFound(CodingKeys(stringValue: "isActive", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"isActive\", intValue: nil) (\"isActive\").", underlyingError: nil))
nil
========================
Optional(__lldb_expr_5.Item(id: 1, name: "Item", isActive: false, date: 2019-08-06 10:55:00 +0000))
Solution 3
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
}
}
Chris Mitchelmore
I'm a technology consultant with a focus on mobile and iOS development. I work mainly with startups including UK App prize winners, building integration for Apple Pay launch partners and working for one of the fastest funded companies ever on seedr. Through work with these companies, and others, I've spent time in The Bakery, completed the Microsoft Ventures accelerator program and taught iOS application development at City University. On top of iOS and backend web development, I can provide mobile strategy and early stage startup advice to my clients. More about me
Updated on June 15, 2022Comments
-
Chris Mitchelmore about 2 years
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.