There's perhaps a few ways you can do it. Off my head, you can use a wrapper struct perhaps, say named AnyModel that conforms to Decodable and works with any model that conforms to Decodable.
In the example below, generic Decodable type T would be Model from your example.
struct AnyModel<T: Decodable>: Decodable {
let result: Result<T, Error>
init(from decoder: Decoder) throws {
do {
let decoded = try T.init(from: decoder)
result = .success(decoded)
} catch let error {
result = .failure(error)
}
}
}
But that won't tell you if there are missing fields I think, but won't fail too. My favorite is using Protocol buffers as I used protocol buffers for everything. Probably an overkill for your use case.
import SwiftProtobuf
extension Message {
public init?(_ data: Data) {
var jsonOptions = JSONDecodingOptions()
jsonOptions.ignoreUnknownFields = false
do {
let message = try Self(jsonUTF8Data: data, options: jsonOptions)
self = message
} catch let err {
guard let error = err as? JSONEncodingError else { return nil }
switch error {
case .missingValue:
print("missing JSON field error: ", error)
default:
print("some other JSONEncodingError: ", error)
}
return nil
}
}
}
Thanks for the reply, I would prefer to avoid vendor code. It turns out, something like this does the trick:
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let result = try JSONDecoder().decode(Model.self, from: data)
if let keys = dict?.keys {
let mirror = Mirror(reflecting: result)
if keys.count != mirror.children.count {
print("Wrong number of keys")
}
}
Update: Even better, the JSONSerialization can be replaced by keyDecodingStrategy.custom to collect all the keys.
One caveat is that you must explicitly declare CodingKeys.
Or if you can use Mirror
struct Strict<T>: Decodable where T: Decodable {
var value: T
init(from decoder: Decoder) throws {
value = try T(from: decoder)
let allKeys = try decoder.container(keyedBy: AllKeys.self).allKeys
let usableKeys = Mirror(reflecting: value).children
if allKeys.count != usableKeys.count {
throw DecodingError.typeMismatch(T.self, .init(codingPath: decoder.codingPath, debugDescription: "Strict \(T.self) does not use all keys from decoder"))
}
}
struct AllKeys: CodingKey {
init?(stringValue: String) { }
init?(intValue: Int) { }
var stringValue: String { return "" }
var intValue: Int? { return nil }
}
}
And simply do
struct Model: Codable {
var key1: String
}
let value = try decoder.decode(Strict<Model>.self, from: data)
Make sure that Mirror reflects the appropriate amount of keys you have. It is possible to have different number of variable if the CodingKey is not synthesised.
Thing could look much better once we get Property Wrapper.