I would start by saying that Code Different's answer is a viable and good answer, but if you seek a different way of doing it, tho working mostly the same way underneath the surface, I have an alternative solution, using the main components of Code Different's answer, resulting in the code below. One of the main differences, is the fact that one JSONDecoder
is being reused on the same JSON, for each struct
you're extracting, using this.
I would also recommend these:
/// Conforming to this protocol, makes the type decodable using the JSONContainer class
/// You can use `Decodable` instead.
protocol JSONContainerCodable: Codable {
/// Returns the name that the type is recognized with, in the JSON.
/// This is overridable in types conforming to the protocol.
static var containerIdentifier: String { get }
/// Defines whether or not the type's container identifier is lowercased.
/// Defaults to `true`
static var isLowerCased: Bool { get }
}
extension JSONContainerCodable {
static var containerIdentifier: String {
let identifier = String(describing: self)
return !isLowerCased ? identifier : identifier.lowercased()
}
static var isLowerCased: Bool {
return true
}
}
struct Product: JSONContainerCodable {
var name: String
var price: Int
}
struct Employee: JSONContainerCodable {
var lastName: String
var department: String
var manager: String
}
/// This class is simply a wrapper around JSONDecoder
class JSONContainerDecoder: Decodable {
private struct AnyCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
init?(stringValue: String) {
self.stringValue = stringValue
}
init(_ string: String) {
stringValue = string
}
}
private let decoder: JSONDecoder
private let container: KeyedDecodingContainer<AnyCodingKeys>
/// Overrides the initializer as specified in `Decodable`.
required init(from decoder: Decoder) throws {
self.decoder = JSONDecoder()
self.container = try decoder.container(keyedBy: AnyCodingKeys.self)
}
/// Factory initializer. Swift (4.2) currently doesn't support overriding the parentheses operator.
static func decoding(_ data: Data, with decoder: JSONDecoder = JSONDecoder()) throws -> JSONContainerDecoder {
return try decoder.decode(JSONContainerDecoder.self, from: myJSON)
}
/// Gets the given type from the JSON, based on its field/container identifier, and decodes it. Assumes there exists only one type with the given field/container identifier, in the JSON.
func get<T: JSONContainerCodable>(_ type: T.Type, field: String? = nil) throws -> T {
return try container.decode(T.self, forKey: AnyCodingKeys(field ?? T.containerIdentifier))
}
/// Short version of the decode getter above; assumes the variable written to already has its type defined.
func get<T: JSONContainerCodable>(field: String? = nil) throws -> T {
return try get(T.self, field: field)
}
}
let myJSON = """
{
"product": {
"name": "PR1",
"price": 20
},
"employee": {
"lastName": "Smith",
"department": "IT",
"manager": "Anderson"
}
}
""".data(using: .utf8)!
let container = try! JSONContainer.decoding(myJSON)
print(try! container.get( Product.self))
print(try! container.get(Employee.self))
Product(name: "PR1", price: 20)
Employee(lastName: "Smith", department: "IT", manager: "Anderson")