JSimpleMapper is a small library for mapping between Java beans. It makes use of Java 1.8 lambda functions and doesn't use any reflection.
Basic Usage
A lot of problems in software development boil down to converting or mapping data.
The Webservice receives JSON data, that needs to be mapped into an object of the application. A database sends a query result, which has to be mapped to an object in our application. A device sends data with a binary protocol, that has to be parsed and mapped to a protocol model in the application. The UI works with a UI-specific ViewModel, that needs to be mapped to the model in the application...
So JSimpleMapper is a small program to map between two Java Beans, which happens a lot in layered applications.
A Java Bean is just a convention (see the Oracle Documentation), that says:
- All properties are private (only getters/setters)
- A public, no-argument constructor
- Implements Serializable
The basic idea of JSimpleMapper is, that you only have to implement an AbstractMapper<TSourceEntity, TTargetEntity>
for mapping between
two entities in your application.
Imagine, you need to convert between a PersonViewModel
and PersonDomainModel
, where the property
PersonViewModel.birthDate
is a String
and PersonDomainModel.birthDate
is a LocalDate
.
// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package de.bytefish.jsimplemapper;
import de.bytefish.jsimplemapper.converters.LocalDateTimeToStringConverter;
import de.bytefish.jsimplemapper.func.ICreator;
import de.bytefish.jsimplemapper.mapping.MappingResult;
import org.junit.Assert;
import org.junit.Test;
import java.time.LocalDate;
public class PersonMapperTest {
// A View Model, that needs to be converted to a Domain Model.
public class PersonViewModel {
private String firstName;
private String lastName;
private String birthDate;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getBirthDate() {
return birthDate;
}
public void setBirthDate(String birthDate) {
this.birthDate = birthDate;
}
}
// A Domain Model with correct types, such as LocalDate for a BirthDate.
public class PersonDomainModel {
private String firstName;
private String lastName;
private LocalDate birthDate;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
}
// Mapper between PersonViewModel and PersonDomainModel.
public class PersonMapper extends AbstractMapper<PersonViewModel, PersonDomainModel> {
public PersonMapper(ICreator<PersonDomainModel> creator) {
super(creator);
mapProperty(PersonViewModel::getFirstName, String.class, PersonDomainModel::setFirstName, String.class);
mapProperty(PersonViewModel::getLastName, String.class, PersonDomainModel::setLastName, String.class);
mapProperty(PersonViewModel::getBirthDate, String.class, PersonDomainModel::setBirthDate, LocalDate.class);
}
}
@Test
public void testMapping() {
PersonMapper mapper = new PersonMapper(() -> new PersonDomainModel());
PersonViewModel personViewModel = new PersonViewModel();
personViewModel.setFirstName("Philipp");
personViewModel.setLastName("Wagner");
personViewModel.setBirthDate("1986-05-12");
MappingResult<PersonDomainModel> result = mapper.map(personViewModel);
Assert.assertEquals(true, result.isValid());
PersonDomainModel personDomainModel = result.getEntity().get();
Assert.assertEquals("Philipp", personDomainModel.getFirstName());
Assert.assertEquals("Wagner", personDomainModel.getLastName());
Assert.assertEquals(LocalDate.of(1986, 5, 12), personDomainModel.getBirthDate());
}
@Test
public void testMapping_Error() {
PersonMapper mapper = new PersonMapper(() -> new PersonDomainModel());
PersonViewModel personViewModel = new PersonViewModel();
personViewModel.setFirstName("Philipp");
personViewModel.setLastName("Wagner");
personViewModel.setBirthDate("1986-05");
MappingResult<PersonDomainModel> result = mapper.map(personViewModel);
Assert.assertEquals(false, result.isValid());
}
}
Advanced Usage: Nested Properties
JSimpleMapper also allows to map complex nested objects. In the following example you can see how to map a flat PersonAddress
class into a rich domain
model Person
, which has a nested Address
property. You simply implement two converters for the mapping PersonAddress -> Address
and the mapping
PersonAddress -> Person
. The PersonMapper
, then defines to use the AddressMapper
for the Person.address
property.
// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package de.bytefish.jsimplemapper;
import de.bytefish.jsimplemapper.func.ICreator;
import de.bytefish.jsimplemapper.mapping.MappingResult;
import org.junit.Assert;
import org.junit.Test;
public class PersonNestedEntityTest {
public class PersonAddress {
private String firstName;
private String lastName;
private String street;
private String city;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public class Person {
private String firstName;
private String lastName;
private Address address;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
public class Address {
private String street;
private String city;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public class AddressMapper extends AbstractMapper<PersonAddress, Address> {
public AddressMapper(ICreator<Address> creator) {
super(creator);
mapProperty(PersonAddress::getStreet, String.class, Address::setStreet, String.class);
mapProperty(PersonAddress::getCity, String.class, Address::setCity, String.class);
}
}
public class PersonMapper extends AbstractMapper<PersonAddress, Person> {
public PersonMapper(ICreator<Person> creator) {
super(creator);
mapProperty(PersonAddress::getFirstName, String.class, Person::setFirstName, String.class);
mapProperty(PersonAddress::getLastName, String.class, Person::setLastName, String.class);
mapProperty(Person::setAddress, new AddressMapper(() -> new Address()));
}
}
@Test
public void testMapping_nested() {
PersonAddress personAddress = new PersonAddress();
personAddress.setFirstName("Philipp");
personAddress.setLastName("Wagner");
personAddress.setStreet("Fake Street 123");
personAddress.setCity("Faketown");
PersonMapper mapper = new PersonMapper(() -> new Person());
MappingResult<Person> result = mapper.map(personAddress);
Assert.assertEquals(true, result.isValid());
Person person = result.getEntity().get();
Assert.assertEquals("Philipp", person.getFirstName());
Assert.assertEquals("Wagner", person.getLastName());
Address address = person.getAddress();
Assert.assertNotNull(address);
Assert.assertEquals("Fake Street 123", address.getStreet());
Assert.assertEquals("Faketown", address.getCity());
}
}