When I'm trying to test my Api Controller with hardcoded Object everything is fine unitil I try to add LocalDate parameter to Object.
My Test:
#RunWith(SpringRunner.class)
#WebMvcTest(ApiTransitController.class)
public class ApiTransitControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TestService testService;
#MockBean
private ReportsService reportsService;
#MockBean
private TransitService transitService;
#Test
public void shouldCreateTransit() throws Exception {
Transit transit = new Transit("London", "Paris", 12L,
LocalDate.of(2018,10,12));
ObjectMapper objectMapper = new ObjectMapper();
String transitJsonString = objectMapper.writeValueAsString(transit);
this.mockMvc.perform(post("/api/transit")
.contentType(MediaType.APPLICATION_JSON)
.content(transitJsonString))
.andExpect(status().isCreated());
verify(transitService).addTransit(eq(new Transit("London", "Paris", 12L,
LocalDate.of(2018,10,12))));
}
}
Model:
#Entity
public class Transit {
#Column(name = "id")
#Id
#GeneratedValue
private Long id;
private String sourceAdress;
private String destinationAdress;
private Long price;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate date;
#JsonSerialize(using=DistanceSerializer.class)
private Long distance;
public Transit(String sourceAdress, String destinationAdress, Long price, LocalDate date) {
this.sourceAdress = sourceAdress;
this.destinationAdress = destinationAdress;
this.price = price;
this.date = date;
}
//getters and setters, equals and hashCode and toString
Api Controller:
#PostMapping("/api/transit")
#ResponseStatus(HttpStatus.CREATED)
public void createTransit(#RequestBody Transit transit){
LOG.info("Saving transit={}", transit);
transitService.addTransit(transit);
}
I tried adding DateTimeFormmater and few other ways, but still I cant pass the test. Thank you for your time.
Try changing this line
verify(transitService).addTransit(eq(new Transit("London", "Paris", 12L,
LocalDate.of(2018,10,12))));
to this:
verify(transitService).addTransit(eq(transit));
The two objects aren't equal, also you don't need to create a new object you can use already created one.
I added JsonSerializer to date of the Model:
Model:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
#JsonSerialize(using=DateSerializerNumberTwo.class)
private LocalDate date;
Serializer:
public class DateSerializerNumberTwo extends StdSerializer<LocalDate> {
private static DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public DateSerializerNumberTwo(){
this(null);
}
protected DateSerializerNumberTwo(Class<LocalDate> t){
super(t);
}
#Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(formatter.format(value));
}
}
And the test passes without any changes in the test code. I think it's beacuse the Json default response of date is "yyyy, mm, dd", not like Local date (yyyy-mm-dd)".
Related
In my elasticsearch cluster I have a document that looks like this:
{
"#version":"1",
"#timestamp":"2021-04-12T14:50:40.298Z",
"message":"{\"#class\": \"com.foobar.PriceChangeEvent\", \"price\":\"4.65\", \"currency\":\"GBP\", \"product\": \"1209381842\", \"meta\": {\"user\": \"TomScott\", \"service\": \"price-manager\"}}",
"exchange":"PriceIncrease",
"service":"price-manager",
"env":"test",
"type":"event",
"user":"TomScott"
}
And a class with the following:
#JsonIgnoreProperties(ignoreUnknown = true)
public class ElasticsearchEventEntry<T extends BaseEvent> {
#JsonProperty("service")
private String service;
#JsonProperty("#timestamp")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime timestamp;
#JsonProperty("env")
private String environment;
#JsonProperty("exchange")
private String exchange;
#JsonProperty("message")
private T event;
#JsonProperty("user")
private String user;
}
Being read using the current method:
ElasticsearchEventEntry<PriceChangeEvent> entry = mapper.readValue(elasticResponse, new TypeReference<ElasticsearchEventEntry<PriceChangeEvent>>() {});
However the message is not parsed straight into a PriceChangeEvent which is understandable as it's a string not "proper" JSON. I have tried to implement a custom deserialiser which would be able to convert the JSON to the event without any luck.
I've attempted to create a custom deserialiser however it's not working with the polymorphic type.
I finally got the custom deserialiser to work for me:
public class ElasticsearchEventMessageDeserialiser extends JsonDeserializer<BaseEvent> {
#Override
public BaseEvent deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
TextNode textNode = jsonParser.readValueAsTree();
ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
return mapper.readValue(textNode.textValue(), BaseEvent.class);
}
}
And simply annotated the field with the new deserialiser:
#JsonIgnoreProperties(ignoreUnknown = true)
public class ElasticsearchEventEntry<T extends BaseEvent> {
#JsonProperty("service")
private String service;
#JsonProperty("#timestamp")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime timestamp;
#JsonProperty("env")
private String environment;
#JsonProperty("exchange")
private String exchange;
#JsonProperty("message")
#JsonDeserialize(using = ElasticsearchEventMessageDeserialiser.class)
#JsonTypeInfo(use = Id.NONE)
private T event;
#JsonProperty("user")
private String user;
}
I'm writing REST service.
I want to get all records by date that I pass in #Path variable.
How Can I do that?
What I tried to do:
Model Class:
#Entity
#Table(name = "test")
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate beginDate;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate endDate;
private String activity;
}
Repository:
#Repository
public interface TestRepository extends JpaRepository<Test, Integer> {
List<Test> findAllByName(String name);
List<Test> findAllByBeginDate(LocalDate date);
}
Service:
#Service
public class TestService {
#Autowired
private final TestRepository testRepository;
public TestService(TestRepository testRepository) {
this.testRepository = testRepository;
}
public List<Test> getAllTestsByBeginDate(LocalDate date) {
return testRepository.findAllByBeginDate(date);
}
}
Controller:
#RestController
#RequestMapping("/api/v1/")
public class TestController {
#GetMapping("test/all/{date}")
public List<Test> getAllTestsByBeginDate(#PathVariable ("date") #DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
return testService.getAllTestsByBeginDate(date);
}
}
When I pass date like this, I get errors:
This should work
#RestController
#RequestMapping("/api/v1/")
public class TestController {
#GetMapping("test/all/{date}")
public List<Test> getAllTestsByBeginDate(#PathVariable #DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
return testService.getAllTestsByBeginDate(date);
}
}
or this link will help
You can global configure any datetime format in application properties. Like:
spring.mvc.format.date=yyyy-MM-dd
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
spring.mvc.format.time=HH:mm:ss
I am using Spring Boot + Spring data Redis example to save Date into the Redis Cache. Although I used #DateTimeFormat #JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd"), but still persistance happening is long value. Look like its a millisecond.
Can somebody guide if I need to set extra configurations to persist date like yyyy-MM-dd.
HGETALL users:1
1) "_class"
2) "com.XXX.entity.User"
3) "userId"
4) "1"
5) "name"
6) "John"
7) "createdDate"
8) "1542043247352"
Entity classes:
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("users")
public class User {
#Id
private Long userId;
private String name;
#DateTimeFormat
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date createdDate;
private List<Group> groups;
}
UPDATE-1:: As per suggestion I implemented, but still not working
CustomDateSerializer.java
#Component
public class CustomDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
Custom Interface
#Retention(RetentionPolicy.RUNTIME)
public #interface MyJsonFormat {
String value();
}
Model class
#MyJsonFormat("dd.MM.yyyy")
#JsonSerialize(using = CustomDateSerializer.class)
private Date createdDate;
I'd advise using LocalDateTime (or LocalDate if you prefer) instead. You can then annotate your fields with
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
using jackson's jsr310 add on:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
By Using Custom Serializer, this can be solved. Ref #https://kodejava.org/how-to-format-localdate-object-using-jackson/#comment-2027
public class LocalDateSerializer extends StdSerializer<LocalDate> {
private static final long serialVersionUID = 1L;
public LocalDateSerializer() {
super(LocalDate.class);
}
#Override
public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
POJO:
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate createdDate;
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);
I'm developing a CRUD application with a REST interface in Java-EE.
I have some entities which contains Date fields. When I want to create an instance of such an entity with a POST request with the JSON in the request body, jax-rs (or the underlying deserializer) complains about parsing the date part.
This is the exception I got:
Servlet.service() for servlet service.JAXRSConfiguration threw
exception java.time.format.DateTimeParseException: Text '2011-11-11'
could not be parsed at index 10
I tried to send this in the request:
{
"title": "testTitle",
"description": "testDescription",
"playtime": 50,
"creationDate": "2011-11-11"
}
How should I define the date in the json to get it parsed successfully? Which is the correct format?
Here is the entity class:
#Entity
#NamedQueries({
#NamedQuery(name = "Movie.findAll", query = "SELECT m FROM Movie m")
})
public class Movie extends AbstractDao{
private String title;
private String description;
#ManyToMany(mappedBy = "movies")
private List<Actor> actors;
#ManyToMany(mappedBy = "movies")
private List<Director> directors;
private int playtime;
#OneToOne(cascade = CascadeType.PERSIST)
private Trailer trailer;
#Temporal(value = TemporalType.DATE)
private Date creationDate;
getters,setters, etc
Here is the jax-rs service:
#Path("/movies")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class MovieRestService {
#Inject
private MovieService movieService;
#GET
public List<Movie> getMovies(){
return movieService.findAll();
}
#Path("{id}")
#GET
public Movie getMovie(#PathParam("id") long id){
return movieService.findById(id);
}
#POST
public Movie addMovie(Movie movie){
return movieService.create(movie);
}
#Path("{id}")
#PUT
public Movie updateMovie(Movie movie, #PathParam("id") long id){
return movieService.update(movie,id);
}
#Path("{id}")
#DELETE
public Movie deleteMovie(#PathParam("id") long id){
return movieService.delete(id);
}
}
Try to use a DateAdapter (XmlAdapter) like the following one:
And annotate:
#XmlJavaTypeAdapter(DateAdapter.class)
private Date creationDate;
You can also annotate at the getter of creationDate
Class:
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date> {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
private static final String PATTERN_T_WITH_SEC = "yyyy-MM-dd'T'HH:mm:ss";
private SimpleDateFormat dateFormat = new impleDateFormat(PATTERN_T_WITH_SEC);
#Override
public String marshal(Date v) throws Exception {
dateFormat.setTimeZone(UTC);
String dateF = dateFormat.format(v);
return dateF;
}
#Override
public Date unmarshal(String v) throws Exception {
dateFormat.setTimeZone(UTC);
Date date = dateFormat.parse(v);
return date;
}
}