I'm using Java library ModelMapper to map my local (JPA) model to a DTO version.
Model
#Entity
public class City {
#Id #GeneratedValue
private Long id;
private String postalCode;
private String name;
// getter/setter/etc.
}
DTO
public class CityDto {
private Long id;
private String postalCode;
private String name;
// getter/setter/etc.
}
Wrapper
I'm going to pass lists of these classes with an additional int, for which I created a genericly typed wrapper class.
public class Wrapper<T> {
private List<T> entities;
private Integer count;
//getter/setter/etc.
}
Question
Now, I want to convert a Wrapper<City> into a Wrapper<CityDto>. I thought that it's the same as converting a List<City> into List<CityDto> as explained in the answer to ModelMapper changes generic type on runtime - weird behavior.
#Test
public void test() {
// prepare
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
Wrapper<City> wrapper = ...
// act
Type type = new TypeToken<Wrapper<CityDto>>() {}.getType();
Wrapper<CityDto> cityDtos = modelMapper.map(wrapper, type);
// check
Assertions.assertTrue(cityDtos.getEntities().get(0) instanceof CityDto);
}
This test fails. I get the same exception as in the aforementioned question
java.lang.ClassCastException: com.my.model.City cannot be cast to com.my.dto.CityDto
The nested list has objects of the type City and not the expected CityDto. What am I doing wrong?
Remark
As a workaround I am doing something like this.
Wrapper<City> wrapper = ...
List<CityDto> cityDtos = modelMapper.map(wrapper.getEntities(), new TypeToken<List<CityDto>>() {}.getType());
Wrapper<CityDto> converted = new Wrapper<>(cityDtos, wrapper.getCount());
That works, but I'd like to know why my original idea is not working.
Related
I would like to write a converter to convert my custom object to DTO and back. How do I approach it?
I have 2 classes Appointment and Doctor which is a subclass of Appointment. I would like to have the converter as simple as possible.
I am not looking for straight answer, would appreciate tips on how to approach it.
below classes have getters and setters:
public class Doctor {
private long id;
private String name;
private String surname;
private String key;
}
public class Appointment {
private long id;
private String description;
private Doctor doctor;
private Date appointmentDate;
}
//converter
public class ConverterComponent {
public AppointmentDTO convert(Appointment appointment){
AppointmentDTO appointmentDTO = new AppointmentDTO();
appointmentDTO.id = appointment.getId();
appointmentDTO.description = appointment.getDescription();
appointmentDTO.doctor = appointment.getDoctor().toString();
appointmentDTO.appointmentDate = appointment.getAppointmentDate().toString();
return appointmentDTO;
}
}
I would like to write another convert(AppointmentDTO appointmentDTO) method in ConverterComponent which will return the Appointment object back.
Could it be done just by parsing Object to json and back again?
Thanks,
I have this code which I would like to use to translate keys and return data to front end:
#GetMapping("pages")
public Page<ContractDTO> pagxes(#RequestParam(value = "page") int page, #RequestParam(value = "size") int size) {
return contractService.findAll(page, size)
//.map(mapper::toDTO);
.map(g -> new ContractDTO(g.getName(), getMerchantName(g.getMerchant_id())));
}
private String getMerchantName(int id) {
Optional<Merchants> obj = merchantService.findById(id);
return obj.get().getName();
}
DTO :
public class ContractDTO {
private Integer id;
.....
private Integer acquirer_id;
private Integer terminal_id;
private String merchant_id;
......
}
How I can rewrite this code .map(g -> new ContractDTO(g.getName(), getMerchantName(g.getMerchant_id()))); to translate from int to String using getMerchantName(int id) only terminal_id and merchant_id and all other variables not to be translated?
I can create constructor in ContractDTO but the code will be huge. Is there some other way?
Error:
The method builder() is undefined for the type ContractDTO
In your case because you want to avoid multiple constructors, You can use a builder design pattern, by using lombok library, it can be more easier, so you can just annotate your class of ContractDTO with this library annotation, and you have every thing to go :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
class ContractDTO {
private Integer id;
private String name;
private Integer acquirerId;
private Integer terminalId;
private String merchantId;
}
then your code can be :
...
.map(g -> ContractDTO.builder()
.name(g.getName())
.merchantName(g.getMerchantId())
.build()
)....
For some reason, I am having issues with the typeconverter in Android Studios. I have tried a variety of solutions from other posts, but continue to get this error. When I tried Arraylist of strings, instead of ingredients, the typeconverter does work.
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
#Entity(tableName = "Recipes")
public class recipesDB {
#PrimaryKey
#NonNull
#ColumnInfo(name = "recipeID")
private int id;
//Foreign Key to be used from Ingredients
#ColumnInfo(name = "name")
private String name;
#ColumnInfo(name = "url")
private String url;
#ColumnInfo(name = "readyInMinutes")
private int readyInMinutes;
//Issue Here
#ColumnInfo(name = "ingredients")
private ArrayList<ingredient> ingredients;
DataConverter class:
public class DataConverter{
#TypeConverter
public static ArrayList<ingredient> toIngredient(String s){
if(s == null){
return null;
}
Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<ingredient>>(){}.getType();
ArrayList<ingredient> ingredients = gson.fromJson(s, listType);
return ingredients;
}
#TypeConverter
public static String getIngredients(ArrayList<ingredient> ingredients){
if(ingredients == null){
return null;
}
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<ingredient>>() {}.getType();
String json = gson.toJson(ingredients);
return json;
}
EDIT
AppDatabase:
#Database(entities = {recipesDB.class}, version = 1, exportSchema = false)
#TypeConverters({DataConverter.class})
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
public abstract recipesDAO recipesdao();
//public abstract pantryDAO pantrydao();
public static AppDatabase getInMemoryDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.inMemoryDatabaseBuilder(context.getApplicationContext(), AppDatabase.class).build();
}
return INSTANCE;
}
public static void destroyInstance(){INSTANCE = null;}
}
So the reason for the issue was due to a duplicate ingredient class outside the scope of my current folder directory. By changing the class ingredient to Ingredient, it must have cleared up the confusion for Room.
NEW ANSWER Turns out that renaming ingredient to Ingredient solved the problem (??) so… maybe keep an eye on those Class names and follow the Java convention of ClassNamesStartWithUpperCase.
OLD ANSWER:
I think, you may need to create a separate “entity” for your ingredient (just like recipesDB is).
Then ingredient sets up a #ForeignKey back to recipesDB.
And finally, you can add a POJO to have both recipesDB and ArrayList<Ingredient>.
I don’t have a sample here with me, but I’ve seen a bunch online.
I think the problem is that your “Converter” doesn’t know how to convert an “Ingredient”. It knows how to convert a List<Ingredient> though, but it’s not the same thing. ;)
class Identifier {
private long id;
private String type;
private List<Status> statuses;
}
class Customer {
private Identifier identifier;
}
class CustomerProfile {
private Customer customer;
}
class CustomerIdentifierDO {
private long id;
}
class CustomeDO {
private CustomerIdentiferDO custID;
}
class CustomerProfileDO {
private String category;
private List<Status> custStatuses;
private CustomeDO customer;
}
#Mapper
public interface CustomerProfileMapper {
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
Customer toCustomer(CustomerDO customerDO);
Identifier toIdentifier(CustomerIdentifierDO identifierDO);
}
Everything works fine till this. Now I want to map custStatuses, category of CustomerProfileDO class to statuses and type of Identifier class. I've no idea how to supply CustomerProfileDO object to toIdentifier mapping method, so that I can include the mapping there itself. I tried following
#Mappings({
#Mapping(target = "customer.identifier.type", source = "category")
})
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
But this nested mapping is overriding all the mapping config of below method. That should not happen.
toIdentifer(CustomerIdentifierDO identifierDO)
Is there any way to achieve this?
Currently MapStruct can pass source parameters to single methods. In order to achieve what you are looking for (without using nested target types you would need to use something like #AfterMapping. It can look like:
#Mapper
public interface CustomerProfileMapper {
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
Customer toCustomer(CustomerDO customerDO);
Identifier toIdentifier(CustomerIdentifierDO identifierDO);
#AfterMapping
default void afterMapping(#MappingTarget CustomerProfile profile, CustomerProfieDO profileDO) {
Identifier identifier = profile.getCustomer().getIdentifier();
identifier.setStatus(profileDO.setStatus());
identifier.setType(profileDO.setCategory());
}
}
I have DTO structure like :
public class ADto{
private String name;
private String id;
private List<BDto> bdtos;
//Created constructor using fields
}
public class BDto{
private String id;
private String code;
private List<CDto> cdtos;
//Created constructor using fields
}
public class CDto{
private String mKey;
private String mVal;
//Created constructor using fields
}
Used Spring MVC for fetching the data.
Below query is working perfectly fine and binding the data :
#org.springframework.data.jpa.repository.Query("select new pkg.ADto(id,name) from AEntity a where a.id=?1")
public ADto getAData(Long id);
How can I fetch the data for the list which is in turn composed of further list using the above method?
If you want to return DTOs instead on enitites, you need to provide mapping between DTOs and entities. With JPQL query, the only option is to provide that mapping in constructor of the resulting object. Therefore, you need to add a constructor to ADto, which accepts BEntities, and map all nested entities to dtos in that constructor. Or in more object oriented way, the new constructor will accept AEntity as the only argument.
This is how it could look like:
getAData() method in the repository (JPQL is slightly modified by adding a.bEntities to result):
#org.springframework.data.jpa.repository.Query("select new pkg.ADto(id,name, a.bEntities) from AEntity a where a.id=?1")
public ADto getAData(Long id);
New constructor in ADto:
public class ADto{
private String name;
private String id;
private List<BDto> bdtos;
public ADto(String id, String name, List<BEntity> bEntities) {
this.id = id; this.name = name;
this.bdtos = new ArrayList<>();
for (BEntity b : bEntities) {
BDto bdto = new BDto(b.id, b.code, b.cEntities);
/* you need to pass cEntities and map them again in the BDto
* constructor, or you may do the apping in ADto constructor
* and only pass mapped values to BDto constructor */
}
}
}
You have to enable eager fetch:
#OneToMany(mappedBy = "adto", fetch = FetchType.EAGER)
private List<BDto> bdtos;
Then you can fetch it like this i.e.:
ADto findById(Long id); // literally!