Passing object as post - reset of LocalDateTime - java

i pass an object ObjectDTO as POST that have some org.joda.time.LocalDateTime parameter.
public class ObjectDTO dto {
...
private LocalDateTime dataesecuzione;
private LocalDateTime oraesecuzione;
public LocalDateTime getDataesecuzione() {
return dataesecuzione;
}
public void setDataesecuzione(LocalDateTime dataesecuzione) {
this.dataesecuzione = dataesecuzione;
}
public LocalDateTime getOraesecuzione() {
return oraesecuzione;
}
public void setOraesecuzione(LocalDateTime oraesecuzione) {
this.oraesecuzione = oraesecuzione;
}
...
}
#PutMapping("/api/get/{id}")
public ResponseEntity<Integer> update(#PathVariable("id") int id, #RequestBody ObjectDTO dto) {
...
}
The problem is that all the LocalDateTime parameter are reset to current date/time.
How can I solve the problem?
Thanks

To let Jackson deserialize the Joda Date Time, register JodaModule in the object mapper bean
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
return objectMapper;
}

Related

Junit test : Properties value are not resolved in Deserializer class

I'm writing some junit tests for my rest webservice. When I run my test, I got my response from the webservice. So I use objectMapper to map the json to my POJO class. However it fails on this line :
return objectMapper.readValue(json, clazz);
Error : com.fasterxml.jackson.databind.JsonMappingException: zoneId (through reference chain: com.customer.CustomerResponse["customerDetails"]->com.customer.CustomerDetails["licenseDate"])
This is because I'm using this annotation in my POJO class : #JsonDeserialize(using = CustomDateDeserializer.class) and in the Deserializer, I have a variable timezone , where the value is taken from properties file. It seems that the object Mapper is not able to resolve this properties in my Deserializer class :
#Value("${current.timezone}")
private String timezone;
Can you please help how to resolve this issue ? Thanks
#Transactional
#Rollback
class CustomerServiceIntegrationTest {
#Autowired
private DbUnitUtils dbUnitUtils;
protected MockMvc mvc;
#BeforeEach
void setup() throws Exception {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#BeforeEach
void setupData() throws Exception {
dbUnitUtils.initialiseDataSet(new File("src/test/resources/dbunit/test-dataset.xml"),
DatabaseOperation.CLEAN_INSERT);
}
#Test
void listCustomer_WhenIdExists_Then_ReturnCustomerInfo() throws Exception {
/*
* setup test-data
*/
Long idCustomer= 15L;
String uri = "/customer/" + 15L;
/*
* invoke endpoint being tested
*/
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(uri).accept(MediaType.APPLICATION_JSON_VALUE))
.andReturn();
/*
* Verification
*/
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
CustomerResponse customerResponse =
mapFromJson(mvcResult.getResponse().getContentAsString(), CustomerResponse.class);
assertEquals(idCustomer, customerResponse.getId());
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module =
new SimpleModule("CustomDateDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(ZonedDateTime.class, new CustomDateDeserializer());
objectMapper.registerModule(module);
return objectMapper.readValue(json, clazz);
}
}
public class CustomerResponse{
String name;
String address;
String phoneNo;
CustomerDetails customerDetails;
//getter and setter
}
public class CustomerDetails {
#JsonDeserialize(using = CustomDateDeserializer.class)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Utils.FORMAT_DATE)
private ZonedDateTime licenseDate;
//getter and setters
}
public class CustomDateDeserializer extends StdDeserializer<ZonedDateTime> {
#Serial
private static final long serialVersionUID = 1L;
#Value("${current.timezone}")
private String timezone;
public CustomDateDeserializer () {
super((Class<?>) null);
}
protected CustomDateDeserializer (Class<?> vc) {
super(vc);
}
#Override
public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws
IOException {
String stringValue = p.getCodec().readValue(p, String.class);
if (StringUtils.isBlank(stringValue)) {
return null;
}
LocalDate localDate = LocalDate.parse(stringValue);
return localDate.atStartOfDay().atZone(ZoneId.of(timezone));
}
}
}

Spring Boot Cannot Deserialize Object That contanis OffsetDateTime

I'm trying to call a rest endpoint which returns a pojo object which looks like this:
public class Process {
#JsonProperty("id")
private String id = null;
#JsonProperty("processDefinitionId")
private String processDefinitionId = null;
#JsonProperty("businessKey")
private String businessKey = null;
#JsonProperty("startedAt")
private OffsetDateTime startedAt = null;
#JsonProperty("endedAt")
private OffsetDateTime endedAt = null;
#JsonProperty("durationInMs")
private Integer durationInMs = null;
#JsonProperty("startActivityDefinitionId")
private String startActivityDefinitionId = null;
#JsonProperty("endActivityDefinitionId")
private String endActivityDefinitionId = null;
#JsonProperty("startUserId")
private String startUserId = null;
#JsonProperty("deleteReason")
private String deleteReason = null;
//constructors and setters+getters
}
Here is the call:
ResponseEntity<Process> responseModel = restTemplate.exchange("http://localhost:8062/processes", HttpMethod.POST, httpEntity, Process.class);
The problem is that i've tried a few methods like ignoring the OffsetDateTime properties or trying to change the format of that date but it will throw this error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.threeten.bp.OffsetDateTime` (no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('2019-10-04T13:20:29.315Z')
Or it will return null :(
What would be a good solution to solve this?
The error states it cannot construct instance of org.threeten.bp.OffsetDateTime. You need to use
java.time.offsetdatetime
Then in your model you can format it whatever way you like e.g.
#JsonProperty("endedAt") //this line is not needed when it is the same as the instance variable name
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd#HH:mm:ss.SSSZ")
private OffsetDateTime endedAt;
I had the same problem with a bean generated by swagger. To solve it I created some serializer and deserializer for date types: org.threeten.bp.LocalDate and org.threeten.bp.OffsetDateTime. And it works well :).
#Bean
#Primary
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
javaTimeModule.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer());
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
public static class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
#Override
public void serialize(OffsetDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
arg1.writeString(arg0.toString());
}
}
public static class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
#Override
public OffsetDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
return OffsetDateTime.parse(arg0.getText());
}
}
public static class LocalDateSerializer extends JsonSerializer<LocalDate> {
#Override
public void serialize(LocalDate arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
arg1.writeString(arg0.toString());
}
}
public static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
return LocalDate.parse(arg0.getText());
}
}

Serializer / Deserializer for OffsetDateTime in Spring Boot

I've created a Serializer / Deserializer for OffsetDateTime in a Spring Boot v1.5.14.RELEASE app. First I create a custom constraint annotation:
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(OffsetDateTime.class, new JsonSerializer<OffsetDateTime>() {
#Override
public void serialize(OffsetDateTime offsetDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(offsetDateTime));
}
});
simpleModule.addDeserializer(OffsetDateTime.class, new JsonDeserializer<OffsetDateTime>() {
#Override
public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return DateUtils.convertToOffsetDateTime(jsonParser.getValueAsString());
}
});
objectMapper.registerModule(simpleModule);
return objectMapper;
}
in the response I see the value correctly formatted, but on the request I got this error
Failed to convert property value of type 'java.lang.String' to required type 'java.time.OffsetDateTime' for property 'fromDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#com.fasterxml.jackson.annotation.JsonFormat java.time.OffsetDateTime] for value '2019-01-01'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2019-01-01]]
and
public static OffsetDateTime convertToOffsetDateTime (String date) {
ZoneId zoneId = ZoneId.of(DateFormat.TIME_ZONE_ID);
ZoneOffset currentOffsetForMyZone = zoneId.getRules().getOffset(Instant.now());
return OffsetDateTime.of( parseLocalDate(date),LocalTime.NOON, currentOffsetForMyZone);
}
and I think the Deserializer is not even called because I added this to throw an Exception, but no exception is throw...
public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
int m = 9 /0 ;
return DateUtils.convertToOffsetDateTime(jsonParser.getValueAsString());
}
public static LocalDate parseLocalDate(String strDate) {
return LocalDate.parse(strDate, DateFormat.DATE_FORMATTER);
}
and the bean:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class HotelData {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime fromDate;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime toDate;
}
and
public final class DateFormat {
public static final String DATE_PATTERN = "yyyy-MM-dd";
public static final String TIME_ZONE_ID = "Africa/Brazzaville";
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter
.ofPattern(DATE_PATTERN)
.withZone(ZoneId.of(TIME_ZONE_ID));
private DateFormat(){}
}
and the problem is on testing:
mockMvc.perform(get("/hotel")
.param("hotelId", "1338767")
.param("fromDate", "2019-01-01")
.param("toDate", "2019-05-21")
.contentType(APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());

How to pass argument to custom JSON deserializer in spring boot app

I want to use my own deserializer in spring boot rest controller. To do its job, it needs some custom configuration - which is provided to it as constructor argument. How can I pass such argument in rest controller?
Here is example.
DTO(with some lombok annotations):
#Getter
#Setter
#RequiredArgsConstructor
#AllArgsConstructor
#JsonDeserialize(using = Deserializer.class)
public class DTO {
private int a;
private int b;
}
Deserializer:
public class Deserializer extends JsonDeserializer<DTO> {
//custom config
int val;
public Deserializer(int value) {
val = value;
}
#Override
public DTO deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{
JsonNode node = p.readValueAsTree();
int a = node.has("a") ? node.get("a").asInt() : -1;
int b = node.has("b") ? node.get("b").asInt() : -1;
//custom config usage
return new DTO(a + val, b + val);
}
}
Controller:
#RestController
#RequestMapping
public class Controller {
//how to pass `val` into deserializer of DTO object?
#PostMapping("/foo")
DTO foo(#RequestBody DTO dto) {
return dto;
}
}
Any help would be appreciated.
you can create a custom ObjectMapper and add your custom serializer to it and at the same time load in a custom value from application.properties.
I think this should work, wrote it from the top of my head.
#Configuration
public class JacksonConfiguration {
#Value("${customValue}")
private int myCustomValue;
#Bean
public ObjectMapper objectMapper() {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule();
module.addSerializer(new Deserializer(myCustomValue));
mapper.registerModule(module);
return mapper;
}
}

ModelMapper and LocalDate - Spring Boot

currently I'm trying to map a dto to a class that also contains LocalDate attribute. Merely I have no success here and the local date field always remains null. So I built a short example, where I followed the pretty helpful hints from Modelmapper to convert from String to LocalDate
So I have a ModelMapper class like this :
#Bean
public ModelMapper createMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(String.class, LocalDate.class);
Provider<LocalDate> localDateProvider = new AbstractProvider<LocalDate>() {
#Override
public LocalDate get() {
return LocalDate.now();
}
};
Converter<String, LocalDate> toStringDate = new AbstractConverter<String, LocalDate>() {
#Override
protected LocalDate convert(String source) {
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate localDate = LocalDate.parse(source, format);
return localDate;
}
};
modelMapper.getTypeMap(String.class, LocalDate.class).setProvider(localDateProvider);
modelMapper.addConverter(toStringDate);
return modelMapper;
}
Furthermore I have a POJO that only has 2 fields, an id and a local date (w/o getters and setters for the sake of readability.
public class JsonLocalDate {
private Long id;
#JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate ld;
public Long getId() {
return id;
}
And I created a test class where I tried to mock the json part by a LinkedHashMap as it comes in the web services I have implemented :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class ModelMapperTest {
#Autowired
ModelMapper mapper;
String jsonLd = "2018-06-11";
LinkedHashMap<String, String> lhm;
#Before
public void init() {
lhm = new LinkedHashMap<>();
lhm.put("id", "1");
lhm.put("ld", jsonLd);
}
#Test
public void checkModelMapper() {
assertNotNull(mapper);
Collection<TypeMap<?, ?>> c = mapper.getTypeMaps();
assertNotNull(c);
for (TypeMap<?, ?> typeMap : c) {
System.out.println("TypeMap : " + typeMap.getConverter().toString());
}
}
#Test
public void testLocalDate() {
LocalDate ld = mapper.map(jsonLd, LocalDate.class);
assertNotNull(ld);
assertEquals(ld, LocalDate.parse(jsonLd,
DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
#Test
public void testLocalDateInObject() {
JsonLocalDate jld = mapper.map(jsonLd, JsonLocalDate.class);
assertNotNull(jld);
assertEquals(jld.getLd(), LocalDate.parse(jsonLd,
DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
}
testLocalDate where I just map the String to LocalDate works fine, while the testLocalDateInObject fails.
Has anybody any idea how I have to deal with LocalDate fields to get them mapped ?
Thanks in advance !
Cheers
Joern
The test failed is because you are trying to map a String to an Object and ModelMapper doesn't know how to map a String to an Object.
So you should try Property Mapping
modelMapper.typeMap(String.class, JsonLocalDate.class)
.addMapping(src -> src, JsonLocalDate::setLd);

Categories