i often run into the situation where i decode something from a serialized form, and need to perform some transformation over one of the fields.
struct SchemaModel:Codable
{
let path:RelativeFilePath
let a:A
let b:B
let c:C
...
}
struct TransformedModel
{
let path:AbsoluteFilePath
let a:A
let b:B
let c:C
...
}
but writing the “map” methods is a lot of effort relative to the complexity of what i am trying to accomplish, because you have to unpack and repack the entire structure to satisfy the type system. (if using generics, SchemaModel<T>.A is not convertible to SchemaModel<U>.A)
is there a better way?
allevato
(Tony Allevato)
2
If the map operations you want to perform are purely based on the types of the members and not the members themselves, you could use @dynamicMemberLookup:
struct A {}
struct B {}
struct C {}
struct RelativeFilePath {
let path: String
init(_ path: String) { self.path = path }
}
struct AbsoluteFilePath {
let path: String
init(relativePath: RelativeFilePath) {
self.path = "/foo/bar/\(relativePath.path)"
}
}
@dynamicMemberLookup
struct Absolute<T> {
let wrapped: T
init(_ wrapped: T) { self.wrapped = wrapped }
subscript<Member>(dynamicMember member: KeyPath<T, Member>) -> Member {
return wrapped[keyPath: member]
}
subscript(dynamicMember member: KeyPath<T, RelativeFilePath>) -> AbsoluteFilePath {
return AbsoluteFilePath(relativePath: wrapped[keyPath: member])
}
}
struct SchemaModel {
let path:RelativeFilePath
let a:A
let b:B
let c:C
}
let model = SchemaModel(path: RelativeFilePath("baz"), a: A(), b: B(), c: C())
print(model.path) // RelativeFilePath(path: "baz")
print(Absolute(model).path) // AbsoluteFilePath(path: "/foo/bar/baz")
You could typealias AbsoluteModel = Absolute<SchemaModel> if you don't want the type projection showing up directly in source code.
in this situation, computing the AbsoluteFilePath is non-trivial and requires context that i do not want to live for as long as the individual AbsoluteModel instances live.
so it’s better to just precompute the AbsoluteFilePath when instantiating AbsoluteModel and return that. however, the wrapped fields are settable, which creates a new class of bugs where the relative path could get overwritten with something not reflected by the absolute path.
i think we could get even more elaborate with overloading and static typing to prevent this, but by then i felt like i was spending more time trying to prevent the type i invented to prevent a completely different class of bug from itself being misused, than actually solving the original problem the code was meant to do.
ibex10
4
Would something like this help, so that the transformation is applied during the decoding/encoding phase?
struct Model: Codable {
enum Path {
case relative (RelativeFilePath)
case absolute (AbsoluteFilePath)
}
let path: Path
let a:A
let b:B
let c:C
...
}