Reputation: 25830
I have two fields: startDate
and endDate
and I need to make sure the end date is equal to or later than the start date. What's the best way to do this?
I would like to ensure that endDate
is deserialized after startDate
, so I can put the logic in its setter method like:
@JsonSetter( "end" )
public void setEnd(String end)
{
this.endDate = parseZonedDateTime( end );
// Invalid
if ( this.endDate.compareTo( this.startDate ) < 0 )
{
// Throw a validation exception
}
}
But that only works if start is guaranteed to be set first.
Upvotes: 7
Views: 4995
Reputation: 877
I'd suggest to create a custom deserializer and register it in your object mapper for this particular class (say MyDateObject
). Assuming MyDateObject
has two fields - startDate
& endDate
, you can impose deserializing startDate
before endDate
using something like this:
public class CustomDeserializer extends JsonDeserializer<MyDateObject> {
@Override
public MyDateObject deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
long startDate = 0;
long endDate = 0;
while (!jsonParser.isClosed()) {
String fieldName = jsonParser.nextFieldName();
if (fieldName == null) {
break;
}
// Check the field name and assign values
// to the corresponding fields
switch (fieldName) {
case "startDate":
startDate = jsonParser.nextLongValue(0L);
break;
case "endDate":
endDate = jsonParser.nextLongValue(0L);
break;
default:
// If you have other fields in the JSON that
// you want to ignore, you can skip them.
jsonParser.skipChildren();
break;
}
}
return generateNewMyDateObject(startDate, endDate);
}
private MyDateObject generateNewMyDateObject(long startDate, long endDate) {
MyDateObject myDate = new MyDateObject();
myDate.setStartDate(startDate);
myDate.setEndDate(endDate);
return myDate;
}
}
Of course the code can be cleaner, but I'll leave it to you as the business expert. Basically, we keep the two values from the JSON content, and only after we got both of them, we generate the MyDateObject
, with startDate
first. Such that you can implement in the setter of endDate
whatever you want, and you can assume startDate
already has a value.
Then, you can register this custom deserializer to your object mapper:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(MyDateObject.class, new CustomDeserializer());
objectMapper.registerModule(module);
And use this object mapper for deserialization:
String jsonString = "{\"endDate\":123,\"startDate\":30}";
MyDateObject customObject = objectMapper.readValue(jsonString, MyDateObject.class);
Note: If you're using Spring Boot, it's even easier. Just define this object mapper as a Bean in your @Configuration class, and let Spring use it for deserialization automatically.
Upvotes: 0
Reputation: 3987
JsonPropertyOrder
should work for the ordering field and we can have the logic of validation in validate()
This would be my approach, hope it will be useful for others.
import java.time.ZonedDateTime; import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({ "startDate", "endDate" })
public class Result {
private ZonedDateTime startDate;
private ZonedDateTime endDate;
// Getters and setters for startDate and endDate
public void setStartDate(ZonedDateTime startDate) {
this.startDate = startDate;
}
public void setEndDate(ZonedDateTime endDate) {
this.endDate = endDate;
}
// Validation method to be called after deserialization
public void validate() {
if (endDate != null && startDate != null && endDate.isBefore(startDate)) {
throw new IllegalArgumentException("End date cannot be before the start date.");
}
}
}
Upvotes: -2
Reputation: 64
You have to use the annotation @JsonPropertyOrder({"startDate", "endDate"})
.
The annotation can be used to define ordering (possibly partial) to use when serializing object properties.
@JsonPropertyOrder({"startDate", "endDate"})
public class MyClass {
private ZonedDateTime startDate;
private ZonedDateTime endDate;
@JsonSetter("startDate")
public void setStartDate(String startDate) {
this.startDate = parseZonedDateTime(startDate);
}
@JsonSetter("endDate")
public void setEndDate(String end) {
this.endDate = parseZonedDateTime( end );
if (this.startDate == null) {
throw new IllegalStateException("startDate must be set before endDate");
}
//invalid
if ( this.endDate.compareTo( this.startDate ) < 0 )
{
//Throw a validation exception
}
}
}
Upvotes: -1
Reputation: 21
In your case, a better approach would be to perform the validation after all properties have been set, not inside the setter. You can use the @JsonCreator and @JsonProperty annotations to achieve this:
public class DateRange {
private ZonedDateTime startDate;
private ZonedDateTime endDate;
@JsonCreator
public DateRange(@JsonProperty("start") String start, @JsonProperty("end") String end) {
this.startDate = parseZonedDateTime(start);
this.endDate = parseZonedDateTime(end);
if (this.endDate.compareTo(this.startDate) < 0) {
throw new IllegalArgumentException("End date must be after start date");
}
}
}
Upvotes: 1
Reputation: 23925
@Hitobat suggests to annotate constructor arguments with JsonCreator instead of trying to annotate fields.
Constructs body can then contain validation logic.
Upvotes: -2
Reputation: 1704
I have two fields: startDate and endDate and I need to make sure the end date is equal to or later than the start date. What's the best way to do this?
I would not try to do this by jackson. Jackson should only focus on converting json to object. The valid of values should be taken care of by jackson. Nor the deserialization order.
Try validating after jackson's converting, either manually or by validation framework like JSR-303.
Upvotes: 7