I've been struggling with the following issue for a few hours now, and I can't figure it out how to make it work:
Spring mapper, in order to convert DB response to DTO:
#Mapper(componentModel = "spring")
public interface ITeamResponseToDtoMapper {
TeamResponseDTO toDto(TeamResponse teamResponse);
}
TeamResponse class:
#Data
#NoArgsConstructor
public class TeamResponse {
private Map<String, List<NameAndType>> teamList;
}
NameAndType class:
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PUBLIC)
public class NameAndType{
private String name;
private String type;
private String team;
}
TeamResponseDTO class:
#Data
#NoArgsConstructor
public class TeamResponseDTO {
private Map<String, List<NameAndTypeDTO >> teamList;
}
NameAndTypeDTO class:
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PUBLIC)
public class NameAndTypeDTO {
private String name;
private String type;
private String team;
}
Basically, 'NameAndType' and 'NameAndTypeDTO' are the same, why it fails to do the conversion?
error: Can't map property "java.util.Map<java.lang.String,java.util.List<com.microservices.teamservice.dataobjects.NameAndType>> teamList" to "java.util.Map<java.lang.String,java.util.List<com.microservices.teamservice.api.dataobjects.NameAndTypeDTO>> teamList". Consider to declare/implement a mapping method:
I think you need to explicit add methods to map the whole chain of classes. On your example the following should work:
#Mapper(componentModel = "spring")
public interface ITeamResponseToDtoMapper {
TeamResponseDTO toDto(TeamResponse teamResponse);
List<NameAndTypeDTO> natListToDTO(List<NameAndType> natList);
NameAndTypeDTO nameAndTypeToDTO(NameAndType nameAndType);
}
regards,
WiPu
Related
Given the following JSON...
{
"loginData": {
"username": "foobar",
"password": "secret"
},
"personalData": {
"givenName": "Foo",
"familyName": "Bar"
}
}
This is what my POJO (with some maybe verbose Lombok annotations) looks like. It gets (de-)serialized by Jackson:
#Builder
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class User {
private LoginData loginData;
private PersonalData personalData;
#Builder
#Getter
#NoArgsConstructor
#AllArgsConstructor
public static class LoginData {
private String username;
private String password;
}
#Builder
#Getter
#NoArgsConstructor
#AllArgsConstructor
public static class PersonalData {
private String givenName;
private String familyName;
}
}
I wonder if it is possible to replicate this structure using the relatively new concept of a Java record. I'm aware of a simple record like:
public record LoginData(String username, String password) {}
But can records also be used for nested/complex objects which shall be converted to JSON with Jackson?
From what I understood you can use records similar to normal objects/classes. This also means you can use records as parameters for other records. I created a simple example for converting your input JSON string to a nested object with records:
Interface defining three records:
public interface IRecord {
record LoginData(String username, String password) {};
record PersonalData(String givenName, String familyName) {};
record Data(LoginData loginData, PersonalData personalData) {};
}
Main class to test:
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{\"loginData\": {\"username\": \"foobar\",\"password\": \"secret\"}, \"personalData\": {\"givenName\": \"Foo\", \"familyName\": \"Bar\"}}";
IRecord.Data user = mapper.readValue(jsonInString, IRecord.Data.class);
System.out.println(user.loginData().username());
}
}
Output:
foobar
I am currently setting up a Rest API server using Spring Boot (v2.5.5), Spring Data Couchbase (v4.2.5) and Couchbase (v6.6.1).
I get a really strange behavior when requesting
count() -> 0
findAll() -> []
Whereas
findById() is returning a result.
My entity:
{"mkey": { "keyContent": "AA", "mkeyStatus": "L" }, "sequences": [ { "direction": "B", "loc1Value": "NCE", "loc2Value": "NYC" } ] }
#Document #Data #AllArgsConstructor #NoArgsConstructor #EqualsAndHashCode public class AirlineProfile {
#Id private String id;
#Field private MKey mkey;
#Field private List<Sequence> sequences;
#EqualsAndHashCode #AllArgsConstructor #NoArgsConstructor #Data static class MKey {
#Field private String keyContent;
#Field private String mkeyStatus;
}
#EqualsAndHashCode #AllArgsConstructor #NoArgsConstructor #Data static class Sequence {
#Field private String loc1Value;
#Field private String loc2Value;
#Field private String direction;
}
}
My repository is extending the CrudRepository.
public interface AirlineProfileRepository extends CrudRepository<AirlineProfile, String> {}
While my Service is the following:
#Service #Qualifier("AirlineProfileServiceImpl") public class AirlineProfileServiceImpl
implements AirlineProfileService {
#Autowired private AirlineProfileRepository airlineProfileRepository;
#Override
public long count() {
return airlineProfileRepository.count();
}
#Override
public List<AirlineProfile> findAll() {
List<AirlineProfile> airlineProfiles = new ArrayList<>();
for (AirlineProfile airlineProfile : airlineProfileRepository.findAll()) {
airlineProfiles.add(airlineProfile);
}
return airlineProfiles;
}
#Override public AirlineProfile findById(String id) {
return airlineProfileRepository.findById(id).orElse(null);
}
}
And my controller the following:
#RestController #RequestMapping("/api") public class AirlineProfileController {
#Autowired AirlineProfileService airlineProfileService;
#GetMapping("/airlineprofile/count") public long count() {
System.out.println("Count");
return airlineProfileService.count();
}
#GetMapping("/airlineprofile/all") public List<AirlineProfile> getAllAirlineProfiles() {
System.out.println("Get all AirlineProfile");
return airlineProfileService.findAll();
}
#GetMapping("/airlineprofile/id={id}") public AirlineProfile getAirlineProfileById(#PathVariable String id) {
System.out.println("Get AirlineProfile for id = " + id);
return airlineProfileService.findById(id);
}
}
I do not know if I missed something at Server or Couchbase side ... :(
Thank you for your help!
Ok, found that:
public interface AirlineProfileRepository extends CrudRepository<AirlineProfile, String> {
#Query("#{#n1ql.selectEntity}")
List<AirlineProfile> findAll();
}
Is working ...
So, I am questioning myself about the usability of findAll() ...
I have a POJO that extends another class ParentDTO both using Lombok with builder Pattern
ChildDTO
#Getter
#ToString
#EqualsAndHashCode(callSuper = false)
#SuperBuilder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ChildDTO extends ParentDTO {
private final Foo foo; // Foo also extends ParentDTO
#Singular("items")
private final List<Item> items;
}
ParentDTO
#Data
#SuperBuilder(toBuilder = true)
public abstract class ParentDTO implements Serializable {
#JsonIgnore private final ObjectMapper objectMapper = new ObjectMapper();
protected ParentDTO () {}
}
My Code Implementation(here I am getting error in gson.toJson)
#PostMapping("/sendRequest")
public ResponseEntity<String> sendMessage(#RequestBody ChildDTO payload) {
final ChildDTO childDTO = payload.toBuilder().bar("xyz").build();
String serializedData = gson.toJson(childDTO); //getting stackoverflow exception in this line.
publishMesaage(serializedData);
return new ResponseEntity<>(HttpStatus.OK);
}
I am seeing the stackoverflow error happening in line gson.toJson(childDTO) when trying to use Gson.
java.lang.StackOverflowError
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(String.java:826)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449)
at java.lang.StringBuffer.append(StringBuffer.java:270)
at java.io.StringWriter.write(StringWriter.java:112)
at com.google.gson.stream.JsonWriter.string(JsonWriter.java:590)
at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:401)
at com.google.gson.stream.JsonWriter.beginObject(JsonWriter.java:308)
at
I have a Object that I would like Jackson to serialize like this...
<AccountsResponse>
<accounts>
<account/>
<account>
<userId>user</userId>
...
</account>
</accounts>
</AccountsResponse>
To try this I create the following class...
#Getter
#Setter
#ToString
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Payload {
#JacksonXmlProperty(localName = "errormessage")
private String errorMessage;
}
#Getter
#Setter
#ToString
public class AccountsResponse extends Payload{
#JsonIgnore
private static Logger LOGGER = LogManager.getLogger(AccountsResponse.class);
#JacksonXmlProperty(localName = "accounts")
private List<Account> accounts = Lists.newArrayList();
public static AccountsResponse mapFromResultSet(ResultSet rs)
throws SQLException
{
AccountsResponse response = new AccountsResponse();
do {
Account acct = Account.mapFromResultSet(rs);
response.getAccounts().add(acct);
} while (rs.next());
return response;
}
public String toXml() throws JsonProcessingException {
ObjectMapper mapper = new XmlMapper();
return mapper.writeValueAsString(this);
}
}
#Getter
#Setter
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Account extends ResultSetParser{
...
}
But when I serialize I get...
<AccountsResponse>
<accounts>
<accounts/>
<accounts>
<userId>user</userId>
...
</accounts>
</accounts>
</AccountsResponse>
As you can see the problem here is the child tags should be account but in fact are accounts. I tried hacking around with the localname but can't find the right mixture of VooDoo. What am I doing wrong?
I would change annotations on account list in AccountsResponse:
public class AccountsResponse extends Payload{
#JacksonXmlElementWrapper(localName = "accounts")
#JacksonXmlProperty(localName = "account")
private List<Account> accounts = Lists.newArrayList();
}
So here's my JSON
{"totalSize":46,"done":true,"records":[{"Name":"Wamu I","Start_Date__c":"2016-09-26T16:56:10.000+0000","Status__c":"Completed","Type__c":"Your were expecting success, but In reality it was I, Dio!!!"}]}
And here are my two entity classes:
#JsonIgnoreProperties(ignoreUnknown = true)
public class EsidesiJobEntity {
#JsonProperty("totalSize")
private #Getter #Setter Integer totalSize;
#JsonProperty("done")
private #Getter #Setter Boolean isDone;
#JsonProperty("records")
private #Getter #Setter List<KarsEntity> records;
#Override
#JsonIgnore
public String toString(){
List<String> recordsObjectString = new ArrayList<String>();
this.records.forEach((record) ->
{
recordsObjectString.add(record.toString());
});
return "{ totalSize:"+this.totalSize+", isDone:"+this.isDone+", records:["+recordsObjectString.toString()+"]";
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class KarsEntity {
#JsonProperty("Name")
private #Getter #Setter String name;
#JsonProperty("Start_Date__c")
private #Getter #Setter String startDate;
#JsonProperty("Status__c")
private #Getter #Setter String status;
#Override
public String toString(){
return "{ name:"+this.name+", startDate:"+this.startDate+", status:"+this.status+"}";
}
}
for some reason, when I map that json string to the EsidesiJobEntity, I get the following error:
Unrecognized field "totalSize"
BUT IT DEFINITELY EXISTS IN BOTH IN BOTH THE JSON AND THE ENTITY!
Here's the code I wrote to map the string to the entity for reference:
EsidesiEntity apexJobResponseEntity;
ObjectMapper apexMapper = new ObjectMapper();
try {
apexJobResponseEntity = apexMapper.readValue(apexResponseString, EsidesiEntity.class);
} ...
Am I missing something really basic?
(BTW, If there's some inconsistency in the Class/Entity names, its because I renamed them before posting them online. Let me know and I'll fix them as I see them. )
Thanks!
You are using Lombok. Jackson can't see your getter and setter methods.
So you have two options:
Do not use Lombok and implement the getter and setter methods
Use Lombok with this additional library: jackson-lombok
If you are using maven, so add jackson-lombok to your pom.xml:
<dependency>
<groupId>com.xebia</groupId>
<artifactId>jackson-lombok</artifactId>
<version>1.1</version>
</dependency>
Configure then your ObjectMapper in this way:
ObjectMapper apexMapper = new ObjectMapper();
apexMapper.setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
[...]
Try this:
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(youJson);
MyPojo pojo = gson.fromJson(jsonElement, MyPojo.class);