Swift Decodable: how to transform one of values during decoding?
Asked Answered
M

2

8

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?

Morganite answered 1/8, 2017 at 5:57 Comment(3)
Compare Swift 4 JSON Decodable simplest way to decode type change – one option would be a wrapper type.Circumspection
I looked to code snippet by your link. Seems i have to implement init(from:) method for all the properties anyway?Morganite
No, the first two examples in my answer using StringCodableMap didn't implement init(from:); they relied on the auto-generated Codable conformance.Circumspection
P
2

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: and init(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

Perfectly answered 24/6, 2021 at 17:32 Comment(1)
Thanks. Interesting. "falling off the cliff" - excellent describing phrase :) Well, at least I glad to hear they considered the problem.Morganite
H
5

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)
Handiwork answered 25/2, 2018 at 1:59 Comment(1)
Thanks! It could be a workaround in some cases if one can accept a violation of the model's immutability.Morganite
P
2

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: and init(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

Perfectly answered 24/6, 2021 at 17:32 Comment(1)
Thanks. Interesting. "falling off the cliff" - excellent describing phrase :) Well, at least I glad to hear they considered the problem.Morganite

© 2022 - 2025 — McMap. All rights reserved.