Don Rhummy
Don Rhummy

Reputation: 25830

How can I specify deserialization order in Jackson?

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

Answers (6)

idanz
idanz

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

Tam
Tam

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

Francesco Pirrone
Francesco Pirrone

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

Haifisch
Haifisch

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

Basilevs
Basilevs

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

John
John

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

Related Questions