I'm using ModelMapper to map a JPA entities to DTO. I have Collections on entities
The dto is generated by wsimport from a wsdl file, but the collection's setters aren't generate
public class sampleEntity{
private String name;
private Collection<String> list;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Collection<String> getList() {
return list;
}
public void setList(Collection<String> list) {
this.list = list;
}
}
public class sampleDTO{
private String name;
private Collection<String> list;
//getters & setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Collection<String> getList() {
return list;
}
//no collection setters with jaxb!!! Use getList().add()
}
I use a simple MapperUtils to map entities and dto
public class MapperUtils {
private static ModelMapper modelMapper = new ModelMapper();
static {
modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
}
private MapperUtils() {
}
public static <D, T> D map(final T entity, Class<D> outClass) {
return modelMapper.map(entity, outClass);
}
public static <D, T> List<D> mapAll(final Collection<T> entityList, Class<D> outCLass) {
return entityList.stream().map(entity -> map(entity, outCLass)).collect(Collectors.toList());
}
public static <S, D> D map(final S source, D destination) {
modelMapper.map(source, destination);
return destination;
}
}
So how to use ModelMapper to use DTO.getXXXX.add () if Entity.XXXX is a Collection?
I don't have any idea if ModelMapper is able to support getList().add() call over a destination during the mapping.
Here are 2 approached that might solve your problem.
Approach 1: enable the field matching
modelMapper.getConfiguration()
.setFieldAccessLevel(AccessLevel.PRIVATE)
.setFieldMatchingEnabled(true);
Approach 2:
Try to generate the setter code with wsimport.
Related
Annotation #JsonTypeInfo is used. When serializing as a list/array, property field isn't generated.
Assuming this is not a bug in Jackson, what's missing to get this working?
Raised an issue in Jackson Databind: https://github.com/FasterXML/jackson-databind/issues/3656
Sample code to demonstrate the issue
Jackson: 2.14.0
JDK: 17
// Base.java
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface Base {
String getName();
void setName(String name);
}
// BaseContainer.java
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface BaseContainer extends Base {
List<Base> getItems();
void setItems(List<Base> items);
}
// BaseImpl.java
public class BaseImpl implements Base {
private String name;
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
}
// ContainerImpl.java
public class ContainerImpl implements BaseContainer {
private String name;
private List<Base> items;
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
#Override
public List<Base> getItems() {
return items;
}
#Override
public void setItems(List<Base> items) {
this.items = items;
}
}
class SerTest {
private static ObjectMapper objectMapper;
#BeforeAll
static void beforeAll() {
objectMapper = new ObjectMapper();
}
#Test
void testSingleBase() {
Base item1 = new BaseImpl();
item1.setName("item1");
String expected = """
{"type":"BaseImpl","name":"item1"}""";
String actual = assertDoesNotThrow(() -> objectMapper.writeValueAsString(item1));
assertEquals(expected, actual);
}
#Test
void testSingleContainer() {
Base item1 = new BaseImpl();
item1.setName("item1");
BaseContainer container1 = new ContainerImpl();
container1.setName("container1");
container1.setItems(List.of(item1));
String expected = """
{"type":"ContainerImpl","name":"container1","items":[{"type":"BaseImpl","name":"item1"}]}""";
String actual = assertDoesNotThrow(() -> objectMapper.writeValueAsString(container1));
assertEquals(expected, actual);
}
// This test fails
// Expected :[{"type":"ContainerImpl","name":"container1","items":[{"type":"BaseImpl","name":"item1"}]},{"type":"BaseImpl","name":"item2"}]
// Actual :[{"name":"container1","items":[{"type":"BaseImpl","name":"item1"}]},{"name":"item2"}]
#Test
void testList() {
Base item1 = new BaseImpl();
item1.setName("item1");
Base item2 = new BaseImpl();
item2.setName("item2");
BaseContainer container1 = new ContainerImpl();
container1.setName("container1");
container1.setItems(List.of(item1));
List<Base> items = new ArrayList<>();
items.add(container1);
items.add(item2);
String expected = """
[{"type":"ContainerImpl","name":"container1","items":[{"type":"BaseImpl","name":"item1"}]},{"type":"BaseImpl","name":"item2"}]""";
String actual = assertDoesNotThrow(() -> objectMapper.writeValueAsString(items));
assertEquals(expected, actual);
}
}
I am new to modelmapper. I have Department and Staff classes in my SpringBoot project. Department class has list of staffs. Using ModelMapper I want to create DepartmentDTO that has staffCount field. I have added Department and DepartmentDTO classes below. How to achive this mapping?
Department class
public class Department {
private Long id;
private String departmentName;
private Set<Staff> staffList = new HashSet<>();
public Department(String departmentName) {
super();
this.departmentName = departmentName;
}
// getters and setters
}
DepartmentDTO class
public class DepartmentDTO {
private Long id;
private String departmentName;
private int staffCount = 0;
public DepartmentDTO(String departmentName) {
this.departmentName = departmentName;
}
// getters and setters
}
I have found solution from this post. I have created DepartmentStaffListToStaffCountConverter class. And used it when adding mappings to modelmapper instance on SpringBootApplication configuration file.
DepartmentStaffListToStaffCountConverter
public class DepartmentStaffListToStaffCountConverter extends AbstractConverter<Set<Staff>, Integer> {
#Override
protected Integer convert(Set<Staff> staffList) {
if(staffList != null) {
return staffList.size();
} else {
return 0;
}
}
}
SpringBootApplication file
#SpringBootApplication
public class SpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplication.class, args);
}
#Bean
public ModelMapper getModelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.typeMap(Department.class, DepartmentDTO.class)
.addMappings(new PropertyMap<Department, DepartmentDTO>() {
#Override
protected void configure() {
using(new DepartmentStaffListToStaffCountConverter()).map(source.getStaffList(), destination.getStaffCount());
}
});
return modelMapper;
}
}
With Jackson, I need to convert an instance of my class Test in CSV but I'm getting problems with a class that contains one list (Inner)
Ex:
public class Test {
String testName;
#JsonUnwrapped
Simple simple;
#JsonUnwrapped
Inner inner;
public Test(String testName, Simple simple, Inner inner) {
this.testName = testName;
this.simple = simple;
this.inner = inner;
}
public String getTestName() {
return testName;
}
public void setTestName(String testName) {
this.testName = testName;
}
public Simple getSimple() {
return simple;
}
public void setSimple(Simple simple) {
this.simple = simple;
}
public Inner getInner() {
return inner;
}
public void setInner(Inner inner) {
this.inner = inner;
}
}
class Inner {
#JsonUnwrapped
List<Person> persons;
public Inner(List<Person> persons) {
this.persons = persons;
}
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Simple {
String simpleName;
public Simple(String simpleName) {
this.simpleName = simpleName;
}
public String getSimpleName() {
return simpleName;
}
public void setSimpleName(String simpleName) {
this.simpleName = simpleName;
}
}
class Main {
public static void main(String[] args) {
Simple simple = new Simple("simple");
Person person = new Person("jesus");
Inner inner = new Inner(Arrays.asList(person));
Test test = new Test("test", simple, inner);
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(Test.class);
try {
String csv = mapper.writer(schema).writeValueAsString(test);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
For objects properties, I used the annotation #JsonUnwrapped as recommended on this link ,
but I get one exception when jackson try to convert the list Inner.persons:
How can I fix that ?
Jackson 2.2.3
First, please excuse the stupid mistakes, I'm on a disconnected network, so I had to retype manually)
I have the following XML:
<orgs>
<org name="Test1">
<item>a</item>
<item>b</item>
</org>
<org name="Test2">
<item>c</item>
<item>d</item>
<item>e</item>
</org>
</orgs>
I have the following class to parse this:
#XmlRootElement(name = "orgs")
#XmlAccessorType(XmlAccessType.FIELD)
public class XmlOrgElements {
private List<Org> orgs;
public List<Org> getOrgs() {
return orgs;
}
public void setOrg(List<Org> orgs) {
this.orgs = orgs;
}
public class Org {
#JacksonXmlProperty(isAttribute = true)
private String name;
private List<Item> items;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Item> getItems() {
return items;
}
public void setName(List<Item> items) {
this.items = items;
}
}
public class Item {
#JacksonXmlText
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
}
But all I'm getting back is "orgs=null". Does anyone know why?
You need to enable unwrapped handling for lists; default is to use "wrapped" format. The best way to diagnose this problem is to start with Java objects, serialize as XML, and see what the output format is.
This gives an idea of how structure differs.
If you want to default to unwrapped style, you can use:
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
mapper.registerModule(module);
There is also an annotation #JacksonXmlElementWrapper:
public class Bean {
#JacksonXmlElementWrapper(useWrapping=false)
public List<Stuff> entry;
}
to change behavior on per-list-property basis.
Here is the answer for those reading along:
#JacksonXmlRootElement(localname = "orgs")
public class Orgs {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Org> org;
public List<Org> getOrg() {
return org;
}
public void setOrg(List<Org> org) {
this.orgs = org;
}
public Orgs() {}
}
public class Org {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlElementWrapper(useWrapping = false)
private List<String> item;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getItem() {
return item;
}
public void setItem(List<String> item) {
this.item = item;
}
}
I would like to merge two lists of objects in Java. One is a list of objects from a database and the second list is from Excel.
The first list of objects contains data like:
[name(str),valid_from(date),valid_to(date),active(flag)]
Objects are equal if names, active flags are equal and dates of obj1,obj2 - valid_from,valid_to are disjoint intervals.
There can be two elements on the list with overlapping date intervals and the same name only if active flag is not equal.
The second list of objects contains the same data and also information about whether to update, remove or create new record on the first list:
[name(str),valid_from(date),valid_to(date),active(flag), action_type(c/u/d)]
//edit: my code is:
public class ClassA {
private String name;
private Date validFrom;
private Date validTo;
private boolean active;
public ClassA(String name, Date validFrom, Date validTo, boolean active) {
this.name = name;
this.validFrom = validFrom;
this.validTo = validTo;
this.active = active;
}
public String getName() {
return name;
}
public Date getValidFrom() {
return validFrom;
}
public Date getValidTo() {
return validTo;
}
public boolean isActive() {
return active;
}
public static void main(String[] args) {
List<ClassA> objectsFromDB = getObjectsFromDB();
List<ClassB> objectsFromFile = getObjectsFromFile();
Map<String, ClassA> objectNameToClassA = buildObjectNameToClassAMap(objectsFromDB);
List<ClassA> objectsToCreate = new ArrayList<ClassA>();
List<ClassA> objectsToUpdate = new ArrayList<ClassA>();
List<ClassA> objectsToDelete = new ArrayList<ClassA>();
for(ClassB object: objects) {
ActionType actionType = object.getActionType();
if(ActionType.CREATE.equals(actionType) {
objectsToCreate.add(object.getObjectA());
}
if(ActionType.UPDATE.equals(actionType) {
objectsToUpdate.add(object.getObjectA());
}
if(ActionType.DELETE.equals(actionType) {
objectsToDelete.add(object.getObjectA());
}
}
}
private static Map<String, ClassA> buildObjectNameToClassAMap(List<ClassA> objects) {
Map<String, ClassA> result = new LinkedHashMap<String, ClassA>();
for(ClassA object: objects) {
result.put(object.getName(), object);
}
return result;
}
}
enum ActionType {
CREATE, UPDATE, DELETE;
}
class ClassB {
private ClassA classA;
private ActionType actionType;
public ClassB(ClassA classA, ActionType actionType) {
this.classA = classA;
this.actionType = actionType;
}
public ClassA getClassA() {
return classA;
}
public ActionType getActionType() {
return actionType;
}
}
I would first define a class for your objects where you override the equals method in order to respect your conditions of equality. Then I would use one of the classes of java.util that implement the Set interface, HashSet for example. Then use the add method on this set with your objects. In set arithmetics, adding is a set union and so it performs a merge.