Model Conversion Implementations
January 26, 2026 • #Architecture #Swift
Having one semantic data model but two different types for implementation to separate domains and architecture layers brings up the question: where to put the conversion code?
I have the following two data types:
DatabaseItemfor the persistence layer which is based onNSManagedObjectand CoreData. It is not safe to send across concurrency domains. Also, it cannot encode and decode the values byFileProviderItemas they are.FileProviderItemas a transport object which is safe to pass across concurrency domains and also conforms to the requirements imposed by the file provider framework.
I need bidirectional conversion from one type to the other. Over the years I have seen different patterns and there probably are even more. Based on the types mentioned above, they could look like the following. For clarity, I reduce it to a single direction at this point.
let fileProviderItem = someDatabaseItem.toFileProviderItem()let fileProviderItem = FileProviderItem(from: someDatabaseItem)let fileProviderItem = Mapper.convertToFileProviderItem(someDatabaseItem)This made me ask myself again what others might argue are the pros and cons for each of them. Spontaneously, none of them convinces me immediately as the go-to pattern and I could also not sell any of them as the best choice. It always depends on the context.
Option 1: Source Type
extension DatabaseItem {
func toFileProviderItem() -> FileProviderItem {
// conversion logic
}
}Pros
- Intuitive to use:
dbItem.toFileProviderItem() - The source type “knows” how to transform itself
- Easy to discover when you have the source object
Cons
- Creates a dependency from database layer to file provider layer
DatabaseItemnow needs to know aboutFileProviderItem- Violates separation of concerns
Option 2: Destination Type
extension FileProviderItem {
init(from databaseItem: DatabaseItem) {
// conversion logic
}
}Pros
- Clean dependency direction, file provider layer can depend on database layer
- Target type controls its own construction
- Very Swifty and idiomatic
Cons
- Can get messy if you need bidirectional conversion
FileProviderItemneeds to understandDatabaseIteminternals
Option 3: Dedicated Mappers
struct ItemMapper {
static func toFileProviderItem(_ databaseItem: DatabaseItem) -> FileProviderItem {
// conversion logic
}
static func toDatabaseItem(_ fileProviderItem: FileProviderItem) -> DatabaseItem {
// conversion logic
}
}Pros
- Best for separation of concerns because both types stay independent
- Bidirectional conversion in one place
- Easy to test in isolation
- Can live in a separate “mapping” layer
- Multiple mapping strategies possible (if needed)
Cons
- Slightly more verbose to use
- One more type to maintain
Summary
In my case, I have my database layer tailored specifically in the context of a file provider extension. Dedicated mappers would have been the choice for bidirectional conversion and clean separation. My database API is already aware of file provider types, though. My sweet spot of compromise is a bit closer to practical reality rather than unnecessary generalization and abstraction. But there is one detail which keeps me from the option of conversion code in the target type: The parsing and conversion of individual property values. It is not straightforward type conversion but requires logic in some cases. Hence the dedicated mapper is the cleanest choice.