I'm using Jackson to handle JSON, I have a custom date serializer for formatting dates in the way that I want but it doesn't abide the #JsonSerialize(include = Inclusion.NON_NULL) annotations. The serializer is below.
If the date is null it still gets written. If I don't use the custom serializer all is fine, null values don't get written. My question is, is there something in the JsonSerializer class that needs to be done to stop null values being written?
public class DateSerializer extends JsonSerializer<Date>
{
#Override
public final void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException
{
SimpleDateFormat sdf = new SimpleDateFormat(MY_FORMAT);
jgen.writeString(sdf.format(date));
}
}
Turns out that it wasn't the serializer at all, but the #JsonSerialize annotation that defaults to include=ALWAYS which overrides the include=NON_NULL on the class. So changing the annotation of the getter works:
changed:
#JsonSerialize(using = DateSerializer.class)
public Date getDate()
{
return date;
}
to:
#JsonSerialize(using = DateSerializer.class,
include=JsonSerialize.Inclusion.NON_NULL)
public Date getDate()
{
return date;
}
Seems that from 2.1.4 to 2.4.2 this behaviour has been fixed and a general
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
does the trick now.
You can crete a Serializaer/Deserializer for your Custom Date format.
For the Deserializer:
public class JsonDateDeserializer extends JsonDeserializer<Date> {
private static final SimpleDateFormat dateFormat;
static {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
dateFormat.setTimeZone(SimpleTimeZone.getTimeZone("UTC"));
}
#Override
public Date deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext) throws IOException {
String date = jsonparser.getText();
if (date != null && !"".equals(date)) {
try {
return dateFormat.parse(date);
} catch (ParseException e) {
throw new JsonParseException(jsonparser, e.getMessage());
}
}
return null;
}
}
For the Serializer:
public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat;
static {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
dateFormat.setTimeZone(SimpleTimeZone.getTimeZone("UTC"));
}
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
And in your POJO:
#Data
public class Person implements Serializable {
private static final long serialVersionUID = -8041031461422721556L;
private Long id;
private String name;
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private Date dob;
}
Related
I am trying to use postman and put these values into the database, but I keep getting an exception.
what im trying to deserialize from postman:
{
"end_date": "2443-11-34 12:43:23",
"start_date": "2443-11-34 12:43:23"
}
The exception that I get:
2020-05-20 10:55:04.572 WARN 4452 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver :
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot
deserialize value of type `java.time.Instant` from String "2443-11-34 12:43:23": Failed to deserialize
java.time.Instant: (java.time.format.DateTimeParseException) Text '2443-11-34 12:43:23' could not be
parsed at index 10; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot
deserialize value of type `java.time.Instant` from String "2443-11-34 12:43:23": Failed to deserialize
java.time.Instant: (java.time.format.DateTimeParseException) Text '2443-11-34 12:43:23' could not be parsed at index 10
at [Source: (PushbackInputStream); line: 3, column: 17] (through reference chain:
com.project.rushhour.model.post.AppointmentPostDTO["end_date"])]
appointment entity:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Appointment extends BaseEntity {
#NotNull
#DateTimeFormat(style = "yyyy-MM-dd HH:mm:ss")
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
#JsonDeserialize(using = JacksonInstantDeserializer.class)
private Instant startDate;
#NotNull
#JsonDeserialize(using = JacksonInstantDeserializer.class)
#DateTimeFormat(style = "yyyy-MM-dd HH:mm:ss")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Instant endDate;
My appointmentDto class:
#Data
#NoArgsConstructor
#AllArgsConstructor
public abstract class AppointmentDTO extends BaseDTO {
#JsonProperty("start_date")
private Instant startDate;
#JsonProperty("end_date")
private Instant endDate;
My AppointmentGetDTO class that I use
public class AppointmentGetDTO extends AppointmentDTO {
}
I also have all of the jackson dependencies
My custom deserializer that I use:
public class JacksonInstantDeserializer extends StdDeserializer<Instant> {
public JacksonInstantDeserializer() { this(null); }
public JacksonInstantDeserializer(Class<?> clazz) { super(clazz); }
#Override
public Instant deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
return Instant.parse(parser.getText());
}
}
You should create a CustomDeserializer for your class AppointmentGetDTO and not Instant class.
We need to register the AppointmentDTO class with the Deserializer. Below I have provided relevant code changes. User debugger and create breakpoints in the deserializer to test the conversion logic.
For further reading checkout: jackson-deserialization and this Stackoverflow answer
Read this for alternate approach: JsonComponent
AppointmentDTO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public abstract class AppointmentDTO {
#JsonProperty("start_date")
private Instant startDate;
#JsonProperty("end_date")
private Instant endDate;
}
AppointmentGetDTO:
#JsonDeserialize(using= JacksonInstantDeserializer.class)
public class AppointmentGetDTO extends AppointmentDTO {
public AppointmentGetDTO(Instant s, Instant e) {
super(s,e);
}
}
Custom Deserializer for AppointmentGetDTO
public class JacksonInstantDeserializer extends StdDeserializer<AppointmentGetDTO> {
public JacksonInstantDeserializer() { this(AppointmentDTO.class); }
public JacksonInstantDeserializer(Class<?> clazz) { super(clazz); }
#Override
public AppointmentGetDTO deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
Instant s=null;
Instant e=null;
if(node.get("start_date") != null) {
s=Instant.parse(node.get("start_date").asText());
}
if(node.get("end_date")!=null) {
e=Instant.parse(node.get("end_date").asText());
}
return new AppointmentGetDTO(s,e);
}
}
Then you can create bean of com.fasterxml.jackson.databind.Module like below:
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(AppointmentGetDTO.class, new JacksonInstantDeserializer());
return module;
}
Controller Mapping for the request:
#PostMapping
public AppointmentDTO convert(#RequestBody AppointmentGetDTO appointmentDTO) {
System.out.println(appointmentDTO.getStartDate());
System.out.println(appointmentDTO.getEndDate());
return appointmentDTO;
}
request json:
{
"end_date": "2443-11-12T12:43:23Z",
"start_date": "2443-11-12T12:43:23Z"
}
i have model (ModelX) with date
#Entity
class ModelX
....
#JsonSerialize(using = DateSerializer.class)
private Long date;
Date Serializer
public class JsonDateSerializer extends JsonSerializer<DateTime>
{
private static DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy");
#Override
public void serialize(DateTime value, JsonGenerator gen,
SerializerProvider arg2)
throws IOException, JsonProcessingException {
gen.writeString(formatter.print(value));
}
}
my controller
#RestController
public class XC {
#GetMapping(value = "/get/{main_key}"
public get ModelX get(#PathVariable("main_key") String main_key) {
return repository.get(main_key);
}
}
the fetch works but my date is a Long but i want a date "dd/MM/yyyy"
Using JSON custom serializer you can format the LONG date
#Entity
class ModelX
....
#JsonSerialize(using = JsonDateCustom.class)
private Long date;
Custom Serializer
#Component
public class JsonDateCustom extends JsonSerializer<Long> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
#Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String formattedDate = dateFormat.format(value);
gen.writeString(formattedDate);
}
}
I have developed a rest service with Spring Boot. I want return a json response with the birthday of the user as milliseconds.
How to serialize a java.time.LocalDateTime object to milliseconds ?
My model class:
#Entity(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "date_of_birth")
private LocalDateTime dateOfBirth;
. . .
}
current response:
{
. . .
"dateOfBirth":[2018,7,25,7,0],
. . .
}
preferred response:
{
. . .
"dateOfBirth": 1532786354419,
. . .
}
use #JsonSerialize(using = CustomSerializer.class)
#Entity(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#JsonSerialize(using = CustomSerializer.class)
#Column(name = "date_of_birth")
private LocalDateTime dateOfBirth;
. . .
}
custom serilizer class:
public class CustomSerializer extends JsonSerializer<LocalDateTime> {
#Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
//add your custom date parser
gen.writeString(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()+"");
}
}
If you don't want to decorate all you objects with #JsonSerialize you can configure the object mapper to always return a long for LocalDateTime.
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateDeserializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
and the de- and serializers.
public class LocalDateSerializer extends JsonSerializer<LocalDateTime> {
#Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(String.valueOf(value.atZone(ZoneId.systemDefault()).toEpochSecond()));
}
}
public class LocalDateDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDateTime.ofInstant(Instant.ofEpochSecond(Long.parseLong(p.getValueAsString())), ZoneId.systemDefault());
}
}
In your example you are using date of birth which could be a LocalDate.
Why dont you store date property as below :
#Temporal(TemporalType.TIMESTAMP)
private Date date = new Date();
It gives date in milliseconds. Formatting it to desired output is responsibility of application layer. Don't do it on entity itself with some annotation.
Is there a better way of writing a Java validator which ensures that a start date is before an end date than writing a class level ConstraintValidator in the following manner:
// VALIDATOR IMPLEMENTATION
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, Object> {
// cannot use LocalDate here...
private String start;
private String end;
#Override
public void initialize(final StartBeforeEndDateValid annotation) {
start = annotation.start();
end = annotation.end();
}
#Override
public boolean isValid(final Object bean, final ConstraintValidatorContext context) {
try {
final String startDateStr = BeanUtils.getProperty(bean, start);
final String endDateStr = BeanUtils.getProperty(bean, end);
final LocalDate startDate = new LocalDate(startDateStr);
final LocalDate endDate = new LocalDate(endDateStr);
return !startDate.isAfter(endDate);
} catch (final Exception e) {
return false;
}
}
}
// USAGE
#StartBeforeEndDateValid(start = "startDate", end = "endDate")
#Entity
public class MyBean {
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate startDate;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate endDate;
...
}
I don't really like the fact that I have to use reflection to extract the 2 date objects from the bean. Unfortunately afaik the validation spec does not specify a way to set only the values you want to validate from the bean.
One way would be to add an interface to MyBean
public interface StartEndDateable {
public LocalDate getStartDate();
public LocalDate getEndDate();
}
public class MyBean implements StartEndDatable {
...
Then you can set the generic type on ConstraintValidator to the new interface instead of Object.
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, StartEndDatable> {
#Override
public void initialize(StartBeforeEndDateValid annotation) {
}
#Override
public boolean isValid(StartEndDatable bean, ConstraintValidatorContext context) {
final LocalDate startDate = bean.getStartDate();
final LocalDate endDate = bean.getEndDate();
return !startDate.isAfter(endDate);
}
}
Obviously any class you then want to validate with the start and end date will have to implement the StartEndDateable (Not the best name, I know, but I'm sure you can think of something better) and define the getStartDate and getEndDate methods.
I am using Spring Hibernate framework. And I have a problem in passing date as json object. Whenever I try to insert an object, it says error 400, request syntactically incorrect.
My controller class
#RequestMapping(value="/hospital", method= RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody Status addHospitalInfo(#RequestBody HospitalInformation hospitalInformation){
try{
if(hospitalService.addHospitalInfo(hospitalInformation)){
return new Status(1,"Success");
}else{
return new Status(0,"Failed");
}
}catch(Exception e){
e.printStackTrace();
return new Status(0,e.getMessage());
}
}
My domain class is
private Integer hospitalId;
private String shortName;
private String name;
private Integer packageId;
private Date implementationDate;
private Date validFrom;
private Date validUpTo;
public enum SubscriptionType{Free,Complimentary,Paid}
private Integer totalUsers;
private Package packages;
public enum Status{Active,Inactive}
private SubscriptionType subscriptionType;
private Status status;
//normal getters and setters for other fields
#Column(name = "implementation_date",
nullable = false)
public Date getImplementationDate() {
return implementationDate;
}
public void setImplementationDate(Date implementationDate)
{
this.implementationDate = implementationDate;
}
#Column(name = "valid_from",
nullable = false)
public Date getValidFrom() {
return validFrom;
}
public void setValidFrom(Date validFrom)
{
this.validFrom =validFrom;
}
#Column(name = "valid_upto",
nullable = false)
public Date getValidUpTo() {
return validUpTo;
}
public void setValidUpTo(Date validUpTo)
{
this.validUpTo =validUpTo;
}
My Dao is
#Transactional
public boolean addHospitalInfo(HospitalInformation hospitalInformation)
throws Exception {
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
if(findByPackageId(hospitalInformation.getPackageId())== null){
return false;
}
else{
session.save(hospitalInformation);
tx.commit();
session.close();
return true;
}
}
#Transactional
public Package findByPackageId(Integer packageId) throws Exception {
Session session=sessionFactory.openSession();
Transaction tx= session.beginTransaction();
List<Package> package1= new ArrayList<Package>();
package1=session
.createQuery("from Package where packageId=?")
.setParameter(0, packageId)
.list();
if (package1.size() > 0) {
return package1.get(0);
} else {
return null;
}
}
And my service class just saves the object into database. So I need help on how to pass date as json object. Thankyou in advance.
To fix your issue you can do one of the two things:
Either use a format that Jackson already recognizes ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd")
or
write a custom deserializer e.g. for yyyyMMdd
public class YourDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
String date = jp.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
and annotate your date fields like
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validFrom;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validUpTo;
Serialization
To have your dates printed the way you want in your JSON response, you can write a custom JSON serializer and annotate the fileds with it, so for yyyyMMdd something like
public class YourDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
jgen.writeString(format.format(value));
}
#Override
public Class<Date> handledType() {
return Date.class;
}
}
and than annotate your field, e.g.
#JsonDeserialize(using = YourDateSerializer.class)
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
Global config
Note also that you can configure your custom serializers to take effect globally, by customizing Jackson's ObjectMapper that is in charge for the conversion. So something like
#Component
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("JsonDateModule", new Version(2, 0, 0, null, null, null));
module.addSerializer(Date.class, new YourDateSerializer());
module.addDeserializer(Date.class, new YourDateDeserializer());
registerModule(module);
}
}
you would need to register your CustomObjectMapper with spring. If you're using the XML config, it would be something like
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="your.package.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>