Be default, Decodable
protocol makes translation of JSON values to object values with no change. But sometimes you need to transform value during json decoding, for example, in JSON you get {id = "id10"}
but in your class instance you need to put number 10
into property id
(or into even property with different name).
You can implement method init(from:)
where you can do what you want with any of the values, for example:
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
latitude = try container.decode(Double.self, forKey:.latitude)
longitude = try container.decode(Double.self, forKey: .longitude)
// and for example there i can transform string "id10" to number 10
// and put it into desired field
}
Thats sounds great for me, but what if i want to change value just for one of the JSON fields and left all my other 20 fields with no change? In case of init(from:)
i should manually get and put values for every of 20 fields of my class! After years of objC coding it's intuitive for me to first call super's implementation of init(from:)
and then make changes just to some fields, but how i can achieve such effect with Swift and Decodable
protocol?
Currently you are forced to fully implement the encode
and decode
methods if you want to change the parsing of even a single property.
Some future version of Swift Codable will likely allow case-by-case handling of each property's encoding and decoding. But that Swift feature work is non-trivial and hasn't been prioritized yet:
Regardless, the goal is to likely offer a strongly-typed solution that allows you to do this on a case-by-case basis with out falling off the "cliff" into having to implement all of
encode(to:
andinit(from:
for the benefit of one property; the solution is likely nontrivial and would require a lot of API discussion to figure out how to do well, hence why we haven't been able to do this yet.- Itai Ferber, lead developer on Swift 4 Codable
https://bugs.swift.org/browse/SR-5249?focusedCommentId=32638
You can use a lazy var
. The downside being that you still have to provide a list of keys and you can't declare your model a constant:
struct MyModel: Decodable {
lazy var id: Int = {
return Int(_id.replacingOccurrences(of: "id", with: ""))!
}()
private var _id: String
var latitude: CGFloat
var longitude: CGFloat
enum CodingKeys: String, CodingKey {
case latitude, longitude
case _id = "id"
}
}
Example:
let json = """
{
"id": "id10",
"latitude": 1,
"longitude": 1
}
""".data(using: .utf8)!
// Can't use a `let` here
var m = try JSONDecoder().decode(MyModel.self, from: json)
print(m.id)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With