Skip to content

Swift macros that generate code to map one type to another.

License

Notifications You must be signed in to change notification settings

pawel-sp/Mappable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mappable

Mappable macro allows to generate code responsible for mapping a type to and from the other type.

Motivation

Mapping one type to another when almost all the properties are the same requires a lot of boilerplate code. One of the examples might be creating a SwiftData type from a domain model:

struct User {
    let id: Int
    let firstName: String
    let lastName: String
}

@Model
final class UserObject {
    let id: Int
    let firstName: String
    let lastName: String
    
    init(id: Int, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }
}

To create UserObject from User and vice versa, the additional extensions might be required:

extension User {
    init(userObject: UserObject) {
        self.init(
            id: userObject.id,
            firstName: userObject.firstName,
            lastName: userObject.lastName
        )
    }
}

extension UserObject {
    convenience init(user: User) {
        self.init(
            id: user.id,
            firstName: user.firstName,
            lastName: user.lastName
        )
    }
}

Having all of that, we can now create User or UserObject by passing the other type of user model.

// User ➡️ UserObject
let user = User(id: 1, firstName: "John", lastName: "Doe")
let userObject = UserObject(user: user)

// UserObject ➡️ User
let userObject = UserObject(id: 1, firstName: "John", lastName: "Doe)
let user = User(userObject: userObject)

Writing such code manually is a very repetitive task. Mappable macro solves this issue by generating convenience init() and model() functions to a class or struct, which allows mapping it to the associated type.

Usage

@Mappable
struct User {
    let id: Int
    let firstName: String
    let lastName: String
}

@Model
@Mappable(to: User.self)
final class UserObject {
    let id: Int
    let firstName: String
    let lastName: String
    
    init(id: Int, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }
}

🔽

@Model
final class UserObject {
    let id: Int
    let firstName: String
    let lastName: String
    
    init(id: Int, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }

    convenience init(model: User) {
        self.init(
            id: model.id,
            firstName: model.firstName,
            lastName: model.lastName
        )
    }

    func model() -> User {
        .init(
            id: id,
            firstName: firstName,
            lastName: lastName
        )
    }
}
@Mappable + custom property name
struct User {
    let id: Int
    let firstName: String
    let lastName: String
}

@Model
@Mappable(to: User.self)
final class UserObject {
    @Map("id")
    let identifier: Int
    let firstName: String
    let lastName: String
    
    init(identifier: Int, firstName: String, lastName: String) {
        self.identifier = identifier
        self.firstName = firstName
        self.lastName = lastName
    }
}

🔽

@Model
final class UserObject {
    let identifier: Int
    let firstName: String
    let lastName: String
    
    init(identifier: Int, firstName: String, lastName: String) {
        self.identifier = identifier
        self.firstName = firstName
        self.lastName = lastName
    }
    
    convenience init(model: User) {
        self.init(
            identifier: model.id,
            firstName: model.firstName,
            lastName: model.lastName
        )
    }

    func model() -> User {
        .init(
            id: identifier,
            firstName: firstName,
            lastName: lastName
        )
    }
}
@Mappable + custom property mapping
struct Identifier {
    let value: Int
}

struct User {
    let id: Identifier
    let firstName: String
    let lastName: String
}

@Model
@Mappable(to: User.self)
final class UserObject {
    @Map(from: \.value, to: Identifier.init(value:))
    let id: Int
    let firstName: String
    let lastName: String
    
    init(id: Int, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }
}

🔽

@Model
final class UserObject {
    let id: Int
    let firstName: String
    let lastName: String
    
    init(id: Int, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }

    convenience init(model: User) {
        self.init(
            id: model.id[keyPath: \.value],
            firstName: model.firstName,
            lastName: model.lastName
        )
    }

    func model() -> User {
        .init(
            id: Identifier.init(value:)(id),
            firstName: firstName,
            lastName: lastName
        )
    }
}

License

Mappable is released under the MIT license. See the LICENSE file for more info.

About

Swift macros that generate code to map one type to another.

Resources

License

Stars

Watchers

Forks

Languages