Skip to content

A Swift macro that automatically generates mapping initializers, eliminating repetitive property assignments between external and local types!

Notifications You must be signed in to change notification settings

artemkalinovsky/AutoMap

Repository files navigation

AutoMap

AutoMap exposes a Swift macro that generates code to automatically map one Swift type to another by providing a convenient initializer. Simply annotate your local type with @AutoMap to transform external or server-side models into your custom Swift structs or classes — no boilerplate required.

Motivation 💡

Manually mapping an external data model to your local domain model can become repetitive and error-prone. For example, if you have a model:

// Example of tedious boilerplate code in a typical project:
extension DomainEntity {
    init(_ apiResponseModel: APIResponseModel.Entity) {
        self.id = apiResponseModel.id
        self.name = apiResponseModel.name
        self.options = apiResponseModel.options.map(Option.init)
    }
}

extension DomainEntity.Option {
    init(_ apiResponseModel: APIResponseModel.Entity.Option) {
        self.id = apiResponseModel.id
        self.minLength = apiResponseModel.minLength
        self.maxLength = apiResponseModel.maxLength
    }
}

You end up writing—and maintaining—these boilerplate initializers over and over again. AutoMap tackles this by automatically generating such mapping code through Swift macros, so you can focus on building features instead of repetitive property assignments.

Usage 🚀

  1. Xcode / SwiftPM
    Make sure you’re using Xcode 15 or later (or Swift 5.9+). Then add AutoMap as a dependency to your project.
  2. Import the Macro In the files where you plan to use the macros, add: import AutoMap
  3. Applying the Macro Annotate your local Swift types with @AutoMap and specify which external type to map from. For example:
   
@AutoMap(from: APIModel.Entity.self, accessControl: .public)
struct DomainEntity: Identifiable, Equatable {
    let id: String
    let name: String
    let options: [Option]

    init(id: String, name: String, options: [Option]) {
        self.id = id
        self.name = name
        self.options = options
    }

    /* This init is generated by `@AutoMap`; you don’t have to implement it yourself.
    public init(_ apiModel: APIModel.Entity) {
        self.init(id: apiModel.id, name: apiModel.name, options: apiModel.options.map(Entity.Option.init))
    }
    */
}

extension DomainEntity {
    @AutoMap(from: APIModel.Entity.Option.self, accessControl: .public)
    struct Option: Identifiable, Equatable {
        let id: String
        let minLength: Int
        let maxLength: Int

        init(id: String, minLength: Int, maxLength: Int) {
            self.id = id
            self.minLength = minLength
            self.maxLength = maxLength
        }

        /* This init is generated by `@AutoMap`; you don’t have to implement it yourself.
        public init(_ apiModel: APIModel.Entity.Option) {
            self.init(id: apiModel.id, minLength: apiModel.minLength, maxLength: apiModel.maxLength)
        }
        */
    }
}

When you build your project, the @AutoMap macro automatically generates a public initializer for DomainEntity and its nested Option struct that accepts an APIModel.Entity (and APIModel.Entity.Option) instance respectively.

That’s all there is to it! You no longer need to write repetitive mapping code—just rely on AutoMap to generate the boilerplate for you.

Known Limitations ⚠️

  1. Swift Tools Version

    • AutoMap relies on Swift Macros, which require Swift 5.9 (Xcode 15) or later. Older Swift versions won’t compile macros.
  2. Property Name Matching

    • The macro expects properties in your local type to match those in the source type—both in name and type signature. If you rename a property or change its type, you’ll need to handle that explicitly.
  3. Complex Type Structures

    • While AutoMap can handle nested types, deeply nested structures or advanced generics may introduce edge cases. Tests for extremely complex type hierarchies are still limited.
  4. Optional & Custom Mappings

    • Currently, AutoMap generates straightforward “one-to-one” initializers. If you need more nuanced mapping logic or special transformation (e.g., converting from String to URL), you may still need to write some custom code or post-processing.
  5. No Partial Initialization

    • The macro generates a single convenience initializer that sets all declared properties. If you need partial initializers, you must implement them manually.
  6. Limited Diagnostics

    • Error messages might not always be descriptive when macro expansion fails (e.g., name mismatches or type mismatches). This is an area of active improvement.
  7. Mandatory Explicit Initializer

    • @AutoMap requires that any annotated struct or class define a designated initializer. Relying on a compiler-synthesized initializer will not work, as it’s overshadowed by the macro. If you don’t provide an explicit initializer, the macro-generated mapping initializer will fail to compile.

Despite these limitations, AutoMap is designed to handle the majority of straightforward property-mapping cases so you can avoid boilerplate code and focus on your application’s core logic.

About

A Swift macro that automatically generates mapping initializers, eliminating repetitive property assignments between external and local types!

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages