java - example - Cómo mapear colecciones en Dozer
dozerbeanmapper (8)
Me gustaría hacer algo como:
ArrayList<CustomObject> objects = new ArrayList<CustomObject>();
...
DozerBeanMapper MAPPER = new DozerBeanMapper();
...
ArrayList<NewObject> newObjects = MAPPER.map(objects, ...);
Asumiendo:
<mapping>
<class-a>com.me.CustomObject</class-a>
<class-b>com.me.NewObject</class-b>
<field>
<a>id</a>
<b>id2</b>
</field>
</mapping>
Lo intenté :
ArrayList<NewObject> holder = new ArrayList<NewObject>();
MAPPER.map(objects, holder);
pero el objeto titular está vacío. También jugué cambiando el segundo argumento sin suerte ...
Citar:
"Las colecciones anidadas se manejan automáticamente, pero es correcto que las colecciones de nivel superior deben ser iteradas. Actualmente no hay una forma más elegante de manejar esto".
Alguien ha descubierto una manera de hacerlo sin una construcción en bucle en su base de código , pero creo que es más fácil (y más legible / mantenible) ponerlo en su código. Esperemos que añadan esta habilidad más pronto que tarde.
Lo he hecho usando Java 8 y Dozer 5.5. No necesitas ningún archivo XML para mapear. Puedes hacerlo en Java.
No necesita ningún mapeo adicional para las listas , solo lo que necesita es
necesita agregar la lista como un campo en el mapeo
. Vea la configuración del bean de muestra a continuación.
Clase de configuracion de primavera
@Configuration
public class Config {
@Bean
public DozerBeanMapper dozerBeanMapper() throws Exception {
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.addMapping( new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(Answer.class, AnswerDTO.class);
mapping(QuestionAndAnswer.class, QuestionAndAnswerDTO.class).fields("answers", "answers");
}
});
return mapper;
}
}
// Las clases Answer y AnswerDTO tienen los mismos atributos
public class AnswerDTO {
public AnswerDTO() {
super();
}
protected int id;
protected String value;
//setters and getters
}
// La clase QuestionAndAnswerDTO tiene una lista de Respuestas
public class QuestionAndAnswerDTO {
protected String question;
protected List<AnswerDTO> answers;
//setters and getters
}
// LET la clase QuestionAndAnswer tiene campos similares como QuestionAndAnswerDTO
// Luego, para usar el mapeador en tu código, autowire it
@Autowired
private DozerBeanMapper dozerBeanMapper;
// in your method
QuestionAndAnswerDTO questionAndAnswerDTO =
dozerBeanMapper.map(questionAndAnswer, QuestionAndAnswerDTO.class);
Espero que esto ayude a alguien a seguir el enfoque de Java en lugar de XML.
Lo que está sucediendo es que está siendo mordido por el borrado de tipo. En el tiempo de ejecución, Java solo ve un ArrayList.class
. El tipo de CustomObject
y NewObject
no están allí, por lo que Dozer está intentando asignar un java.util.ArrayList
, no su CustomObject
a NewObject
.
Lo que debería funcionar (totalmente no probado):
List<CustomObject> ori = new ArrayList<CustomObject>();
List<NewObject> n = new ArrayList<NewObject>();
for (CustomObject co : ori) {
n.add(MAPPER.map(co, CustomObject.class));
}
Me enfrenté a un problema similar y decidí usar un método de utilidad genérico para evitar la iteración cada vez que necesitaba realizar dicho mapeo.
public static <T, U> List<U> map(final Mapper mapper, final List<T> source, final Class<U> destType) {
final List<U> dest = new ArrayList<>();
for (T element : source) {
dest.add(mapper.map(element, destType));
}
return dest;
}
El uso sería entonces algo como:
final List<CustomObject> accounts.....
final List<NewObject> actual = Util.map(mapper, accounts, NewObject.class);
Posiblemente esto podría ser simplificado aún más.
No es realmente una mejora, es más como un azúcar sintáctico que se puede lograr gracias a la Guava (y lo más probable es que sea posible con Apache Commons ):
final List<MyPojo> mapped = Lists.newArrayList(Iterables.transform(inputList, new Function<MyEntity, MyPojo>() {
@Override public MyPojo apply(final MyEntity arg) {
return mapper.map(arg, MyPojo.class);
}
}));
Esto también puede convertirse en una función genérica, como se sugiere en otras respuestas.
Para ese caso de uso, una vez escribí una pequeña clase de ayuda:
import java.util.Collection;
/**
* Helper class for wrapping top level collections in dozer mappings.
*
* @author Michael Ebert
* @param <E>
*/
public final class TopLevelCollectionWrapper<E> {
private final Collection<E> collection;
/**
* Private constructor. Create new instances via {@link #of(Collection)}.
*
* @see {@link #of(Collection)}
* @param collection
*/
private TopLevelCollectionWrapper(final Collection<E> collection) {
this.collection = collection;
}
/**
* @return the wrapped collection
*/
public Collection<E> getCollection() {
return collection;
}
/**
* Create new instance of {@link TopLevelCollectionWrapper}.
*
* @param <E>
* Generic type of {@link Collection} element.
* @param collection
* {@link Collection}
* @return {@link TopLevelCollectionWrapper}
*/
public static <E> TopLevelCollectionWrapper<E> of(final Collection<E> collection) {
return new TopLevelCollectionWrapper<E>(collection);
}
}
Entonces llamaría a dozer de la siguiente manera:
private Mapper mapper;
@SuppressWarnings("unchecked")
public Collection<MappedType> getMappedCollection(final Collection<SourceType> collection) {
TopLevelCollectionWrapper<MappedType> wrapper = mapper.map(
TopLevelCollectionWrapper.of(collection),
TopLevelCollectionWrapper.class);
return wrapper.getCollection();
}
Solo inconveniente: mapper.map(...)
advertencia "sin mapper.map(...)
" en mapper.map(...)
debido a que la interfaz de Dozers Mapper no maneja tipos genéricos.
Puede implementar su propia clase de asignador que extenderá el asignador de topadora. Ejemplo: Cree una interfaz que agregue un método adicional al Dozer Mapper:
public interface Mapper extends org.dozer.Mapper {
<T> List<T> mapAsList(Iterable<?> sources, Class<T> destinationClass);
}
Siguiente paso: escriba su propia clase de asignador implementando la interfaz anterior.
Agregue el siguiente método a su clase de implementación:
public class MyMapper implements Mapper {
@Override
public <T> List<T> mapAsList(Iterable<?> sources, Class<T> destinationClass) {
//can add validation methods to check if the object is iterable
ArrayList<T> targets = new ArrayList<T>();
for (Object source : sources) {
targets.add(map(source, destinationClass));
}
return targets;
}
//other overridden methods.
}
Espero que esto ayude
Puedes hacerlo así:
public <T,S> List<T> mapListObjectToListNewObject(List<S> objects, Class<T> newObjectClass) {
final List<T> newObjects = new ArrayList<T>();
for (S s : objects) {
newObjects.add(mapper.map(s, newObjectClass));
}
return newObjects;
}
y úsalo:
ArrayList<CustomObject> objects = ....
List<NewObject> newObjects = mapListObjectToListNewObject(objects,NewObject.class);