Converter is an automatic convention based object mapper similar to AutoMapper for Swift.
There comes time where you have two models, with similar properties, that follow the same naming standard. Whether it's a domain model for your persistance layer, a DTO for an API request, or just a slimmed down version. Mapping the object between types usually results in a lot of boiler plate code manually mapping each property. Converter takes the hassle away and does it automatically base off the naming convention.
For example we have the Source
type. We want to be able to convert objects of the Source
type to Destination
. So first we have to create the conversion. The conversions are static and only need to be created once, usually when the application is started.
try createConversion(from: Source.self, to: Destination.self)
Once the conversion is created to convert the object:
let newObject = try convert(mySourceObject, to: Destination.self)
It's that easy! No boiler plate code to write 🎉.
If you use the AWS Mobile Hub when it generates the domain models for the DynamoDb instance every property is prefixed with an underscore, and they are classes by default. Say you would like to use structs instead and not have the underscore you would normally have to write code to convert the AWS domain model to the struct model. Converter will do this automatically!
class PetDomain: AWSDynamoDBObjectModel, AWSDynamoDBModeling {
var _userId: String?
var _name: String?
var _age: Int
}
struct Pet {
var userId: String?
var name: String?
var age: Int
}
try createConversion(from: PetDomain.self, to: Pet.self)
let pet = try convert(domain, to: Pet.self)
Say you have two models that are similar but the property names don't match perfectly. Or even one property in the destination type is a combination of two on the source type. For example we have a Person
type and a Teacher
type. Person
has firstName
and lastName
while the Teacher
object only has name
.
struct Person {
var id: Int
var firstName: String
var lastName: String
var age: Int
}
struct Teacher {
var id: Int
var name: String
var age: Int
}
To specify which property to use for the Teacher.name
property you can choose from a KeyPath
on the source type.
try createConversion(from: Person.self, to: Teacher.self)
.for(property: "name", use: \.firstName)
Or for more flexibility you can use a closure to allow you to use multiple properties on the source object.
try createConversion(from: Person.self, to: Teacher.self)
.for(property: "name", use: {"\($0.firstName) \($0.lastName)"})
Say on one conversion you would like to omit one property from being set. Example:
try createConversion(from: Person.self, to: Teacher.self)
.ignore(property: "lastName")
Converter tries to be intelligent when matching properties on the source to the destination type. It will automatically handle mapping to and from different casing styles, including camelCasing
, PascalCasing
, and snake_casing
. So for example SomePropertyName
or some_property_name
can be instantly mapped to and from somePropertyName
Private properties sometimes are noted with an underscore at the beginning. Converter will automatically convert them as well. So for example firstName
can be mapped automatically to and from _firstName
.
If you would like the conversion to match properties only when the names are an exact match then when the conversion if created you can specify a strict
matching policy.
Example:
try createConversion(from: Person.self, to: Teacher.self, matching: .strict)
Converter also has the ability to flatten out objects, allowing you to automatically pull values out of nested objects with no extra work. For example we have the Person
type which has a property of type Pet
. Pet
objects have a name property. Our destination type would like the pet name to be at the top level. Person
's pet property name is pet
and we would like to flatten out it's name
property. So by naming our destination property petName
, Converter will translate that too pet.name
and grab the value.
struct Pet {
var name: String
}
struct Person {
var pet: Pet
}
struct FlattenedPerson {
var petName: String
}
try createConversion(from: Person.self, to: FlattenedPerson.self)
let source = Person(pet: Pet(name: "Marley"))
let person: Destination = try convert(source)
print(person.petName) // prints Marley
Conversions can be made from an array of a source type to an array of a destination type. There is no need to create an extra conversion for an array, just the type within. Example:
try createConversion(from: Source.self, to: Destination.self)
let new: [Destination] = try convert(arrayOfSourceObjects)
To get and set the values dynamically it uses my other library Runtime. Converter will automatically convert two objects when the property names are the same, and allows for custom mappings. The createConversion(from: to:)
method defines a path of how it will be converted. Some of the more expensive operations are done once when the conversion is created making the conversion just a wee bit faster.
Anyone is welcome to contribute! It is still a work in progress. If there is something you would like implemented either send a pull request or open an issue. Any questions also feel free to open an issue.
Converter is available under the MIT license. See the LICENSE file for more info.