1. Introduction
In our Java objects, we generally use some kind of collection, be it a List or Map. When mapping collection-typed fields, we generally need special support from the underlying mapper library. In this tutorial, we're going to investigate Orika's collection support.
2. Sample Application
We'll first look at our sample application.
We have the Person and PersonDto classes:
public class Person {
private String firstName;
private String lastName;
// Getters and setters...
}
public class PersonDto {
private String name;
private String surname;
// Getters and setters...
}
Note that the field names are different.
Then we have the Family and FamilyDto classes:
public class Family {
private List<Person> parents;
// Getters and setters...
}
public class FamilyDto {
private List<PersonDto> parents;
// Getters and setters...
}
Both of the classes have a parents field but with different types.
Throughout the examples we'll add more fields to the classes.
3. Mapping List Fields
We'll start with a common scenario where we map one List field to another. Remember that Orika performs mapping recursively until it reaches a primitive type. This means that if the list elements aren't primitive, Orika performs mapping on the list elements too.
public BoundMapperFacade<Family, FamilyDto> mapListFields() {
final DefaultMapperFactory mapperFactory = new Builder().build();
mapperFactory.classMap(Family.class, FamilyDto.class)
.byDefault()
.register();
mapperFactory.classMap(Person.class, PersonDto.class)
.field("firstName", "name")
.field("lastName", "surname")
.byDefault()
.register();
return mapperFactory.getMapperFacade(Family.class, FamilyDto.class);
}
Firstly, we're defining a mapper for Family and FamilyDto using the ClassMapBuilder API. Then we're defining an additional mapper for the Person and PersonDto classes. This mapper is required for the correct mapping of the list elements.
4. Mapping Elements of Lists
Next, we'll map a specific list element to another field. Orika allows us to access an element by its index when defining a field mapping:
public class FamilyDto {
// Other fields
private PersonDto mother;
private PersonDto father;
}
public BoundMapperFacade<Family, FamilyDto> mapUsingListElements() {
final DefaultMapperFactory mapperFactory = new Builder().build();
mapperFactory.classMap(Family.class, FamilyDto.class)
.field("parents[0]", "mother")
.field("parents[1]", "father")
.byDefault()
.register();
mapperFactory.classMap(Person.class, PersonDto.class)
.field("firstName", "name")
.field("lastName", "surname")
.byDefault()
.register();
return mapperFactory.getMapperFacade(Family.class, FamilyDto.class);
}
In the FamilyDto class, we're declaring two new fields: mother and father. We're telling Orika to use parents[0] for mother and parents[1] for father. Note that Orika uses this information for the reverse mappings too. Then we're registering another mapper for a proper mapping between Person and PersonDto instances.
We can also access the fields of a list element using the dot '.' notation. This enables us to construct nested field expressions:
public class FamilyDto {
// Other fields...
private String motherName;
private String fatherName;
}
public BoundMapperFacade<Family, FamilyDto> mapUsingListElementsWithNestedAccess() {
final DefaultMapperFactory mapperFactory = new Builder().build();
mapperFactory.classMap(Family.class, FamilyDto.class)
.field("parents[0].firstName", "motherName")
.field("parents[1].firstName", "fatherName")
.byDefault()
.register();
return mapperFactory.getMapperFacade(Family.class, FamilyDto.class);
}
Here, we're getting the first element of parents and then navigating to its firstName field. We're also doing the same for the second element.
5. Mapping Elements of Lists in Bulk
In the previous example, we used a single List element when defining a mapping. Now we'll map the List elements in bulk.
public class FamilyDto {
// Other fields...
private List<String> parentNames;
}
public BoundMapperFacade<Family, FamilyDto> mapUsingListElementsInBulk() {
final DefaultMapperFactory mapperFactory = new Builder().build();
mapperFactory.classMap(Family.class, FamilyDto.class)
.field("parents{firstName}", "parentNames{}")
.byDefault()
.register();
return mapperFactory.getMapperFacade(Family.class, FamilyDto.class);
}
Here, we're adding parentNames to the FamilyDto class. Then we're populating it with the first names of the parents. To summarize:
- parents{firstName} represents the firstName field of every element in the parents list.
- parentNames{} represents the target list itself, without any field navigation.
6. Mapping Keys and Values of Maps
So far, we only worked with the List fields. Now we'll also examine the usage of Map fields.
Let's first modify the Person and PersonDto classes:
public class Person {
// Other fields...
private Map<String, Pet> petsBySpecies;
}
public class PersonDto {
// Other fields...
private PetDto dog;
private PetDto cat;
}
Person holds the pets grouped by their species. PersonDto, on the other hand, flattens the structure and holds a specific reference to the cat and dog instances.
Just like we can access a List element by its index, we'll now access a Map value by its key:
public BoundMapperFacade<Person, PersonDto> mapUsingMapElements() {
final DefaultMapperFactory mapperFactory = new Builder().build();
mapperFactory.classMap(Person.class, PersonDto.class)
.field("firstName", "name")
.field("lastName", "surname")
.field("petsBySpecies['cat']", "cat")
.field("petsBySpecies[\"dog\"]", "dog")
.byDefault()
.register();
return mapperFactory.getMapperFacade(Person.class, PersonDto.class);
}
In this mapper configuration, petsBySpecies['cat'] represents the entry whose key is cat. Note that we're using single quotes to write the key. But it's also fine to use double quotes by escaping them - petsBySpecies[\"dog\"].
7. Mapping Keys and Values of Maps in Bulk
Now, we'll map the keys and values of a Map in bulk:
public class FamilyDto {
// Other fields...
private Map<String, PersonDto> parentsByName;
}
public BoundMapperFacade<Family, FamilyDto> mapUsingMapElements() {
final DefaultMapperFactory mapperFactory = new Builder().build();
mapperFactory.classMap(Family.class, FamilyDto.class)
.field("parents{firstName}", "parentsByName{key}")
.field("parents{}", "parentsByName{value}")
.byDefault()
.register();
// Other code...
}
In this example, we're adding the parentsByName field. To represent the keys of this new Map, we're using the parentsByName{key} expression. Similarly, to represent the values, we're using parentsByName{value}. In the end, parents{firstName} is copied to the keys, and the parents list itself is copied to the values.
8. Running Mapper on Collections
Lastly, instead of using collections when configuring the mappers, we'll now look at how we can run a mapper on a collection of objects. MapperFacade provides methods that accept arrays or Iterables. These methods return the mapped objects in a specific collection type.
As an example, let's examine the mapAsList method:
final MapperFacade mapperFacade = mapperFactory.getMapperFacade();
final List<Person> people = // Get the person instances
final List<PersonDto> personDtos = mapperFacade.mapAsList(people, PersonDto.class);
Besides mapAsList, MapperFacade provides mapAsSet, mapAsArray, and others.
9. Summary
In this tutorial, we've examined how we can work with the collection-typed fields using the Orika mapping library. We first detailed the mapping of the List fields. Then we focused on the Map fields. Lastly, we peeked into the convenience methods that perform mapping on a collection of objects.
As always, the source code for all examples is available on Github.