Cloud Cloud

Iva Horn

Notes from a software engineer for iPhone, iPad and Mac apps.

Memoji

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:

  • DatabaseItem for the persistence layer which is based on NSManagedObject and CoreData. It is not safe to send across concurrency domains. Also, it cannot encode and decode the values by FileProviderItem as they are.
  • FileProviderItem as 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.

a memoji

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
  • DatabaseItem now needs to know about FileProviderItem
  • 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
  • FileProviderItem needs to understand DatabaseItem internals

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.