This. Mantle uses NSValueTransformer
to accomplish this sort of thing. It would look something like this in Swift:
struct Person: Decodable {
let age: Int
let name: String
static var decodeAge: (Any) -> Int = { value in
if let ageString = value as? String {
return Int(ageString)
}
// Throw an error or return a default value, etc
}
}
Mirroring this behavior, I would like the runtime to see that a transformer for age
exists on the type Person
and use that to transform the value keyed by age
before assigning it. More generally, this could use some new Transformer
type that works similar to NSValueTransformer
to provide two-way transformation, rather than the one-way transform above. The standard library could (and should) provide default transformers for common transformations, transforming between String
and Int
, allowing for something like this:
struct Person: Codable {
let age: Int
let name: String
static var ageTransformer = Transformer.stringToInt
// Or, ideally...
// static var ageTransformer = Transformer<String, Int>()
}
These types of transformations are all too common and necessary in many JSON APIs. Plenty of APIs provide numbers or strings in place of booleans (i.e. 0
/1
or "true"
/"false"
in place of true
/false
) for example, right behind the most common problem of sending everything as a string so that numbers need to be transformed to numbers, lest they be decoded as Strings.
Discourse's own API is extremely guilty of this and similar crimes, which made me give up on using Codable to write a Swift Forums app. To name one, Discourse posts have a bookmarked
field on them. IIRC, this field is null
if you've never bookmarked that post before, and when you bookmark it, it becomes true
, and when you un-bookmark it, it becomes false
. With that in mind, whatever transformer solution we come up with should not only handle transforming from T
to U
, but from T?
to U
. This API could look like any of these:
static var ageTransformer = Transformer.stringToInt.allowingNil
static var ageTransformer = Transformer<String?, Int>()
static var upvotedTransformer = Transformer<String, Bool>()
static var bookmarkedTransformer = Transformer<Bool?, Bool>()
Someone is probably going to say that property wrappers would be good for this sort of thing. They probably would be, but I have a feeling they would make debugging more difficult. The replies to this comment share my thoughts. So does this:
I would like to see a dedicated transformer type, unless someone has a compelling reason why we should take a different approach. That said, my approach is somewhat verbose. I'm curious to see what ideas others can come up with here. Reply to me with your ideas!
The only other thing I have to add is that every suggestion here should be taken seriously. As an example:
This may seem like a minor thing to the core team, but this feature is essential to minimizing boilerplate around a common task. I think everyone's biggest problem with Codable right now comes down to boilerplate. Codable seems like it was designed with the idea that the programmer controls the backend. When you do not control the backend, Codable quickly becomes a pain to use.
Edit: I forgot one thing.
Classes!
Codable has awful support for classes. This may also be a good opportunity to add synthesized initializers to classes subclasses.