FasterXML / jackson-annotations

Core annotations (annotations that only depend on jackson-core) for Jackson data processor
https://github.com/FasterXML/jackson
Apache License 2.0
1.03k stars 330 forks source link

Unexpected behavior with @JsonInclude(JsonInclude.Include.NON_EMPTY) and Date serialization #111

Closed stefan-huettemann closed 7 years ago

stefan-huettemann commented 7 years ago

Hi.

Is this really what is intended? I would consider this a bug.

I see unexpected behavior using @JsonInclude(JsonInclude.Include.NON_EMPTY) with java.util.Date serialization.

Setting a Date to 1970-01-01 results in an empty JSON object since NON_EMPTY seems to consider 1970-01-01 as empty (0 time stamp value) where as I consider 1970-01-01 a perfectly valid NON_EMPTY value ;)

Consider this code:

public class TestJacksonJsonFormat {

    public static void main(String[] args) throws JsonProcessingException {

        final ObjectMapper theObjectMapper = new ObjectMapper();
        final TestDTO theTestDTO = new TestDTO();

        theTestDTO.setDate(new Date(0));  // set to "1970-01-01"
        System.out.printf("1970-01-01: %s\n", theTestDTO.getDate());
        System.out.printf("Serialized: %s\n", theObjectMapper.writeValueAsString(theTestDTO));

        theTestDTO.setDate(new Date(1));  // set to "1970-01-01T00:00:00.001+0000"
        System.out.printf("1970-01-01 + 1 msec: %s\n", theTestDTO.getDate());
        System.out.printf("Serialized: %s\n", theObjectMapper.writeValueAsString(theTestDTO));

    }

    @JsonInclude(JsonInclude.Include.NON_EMPTY) // Note: works with NON_NULL
    private static class TestDTO {

        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone = "UTC")
        private Date date;

        Date getDate() {
            return date;
        }

        void setDate(final Date aDate) {
            date = aDate;
        }
    }
}

Note: setting disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) does not change the effect.

cowtowncoder commented 7 years ago

@stefan-huettemann First things first: which Jackson version? Jackson 2.6 did have more extensive selection of "empty" values, but with 2.7 this was reverted and another value, NON_DEFAULT, could be used to exclude "default" values.

stefan-huettemann commented 7 years ago

@cowtowncoder Hi. I use version 2.8.5 (as "bundled" in spring-boot 1.4.3).

Thanks for pointing me to NON_DEFAULT. Unfortunately the NON_DEFAULT annotation does not really help me ;)

The behavior of NON_EMPTY and NON_DEFAULT both confuse me.

NON_EMPTY: will exclude a Date attribute that is explicitly set to the value 1970-01-01 where as an int attribute, that is not set programmatically (default) will be serialized to 0 and an unset (default) attribute of type boolean will be serialized to false.

final Test_NON_EMPTY testNonEmpty = new Test_NON_EMPTY();
testNonEmpty.setDate(new Date(0));  // set to "1970-01-01"

results in:

toString: < date : Thu Jan 01 01:00:00 CET 1970' | counter : 0 | flag : false >
NON_EMPTY   Serialized: {"counter":0,"flag":false}

NON_DEFAULT: will include the explicitly Date value as is expected but will exclude an explicitly set int attribute with value 0:

final Test_NON_DEFAULT testNonDefault = new Test_NON_DEFAULT();
testNonDefault.setDate(new Date(0));  // set to "1970-01-01"
testNonDefault.setCounter(0);

results in:

toString: < date : Thu Jan 01 01:00:00 CET 1970' | counter : 0 | flag : false >
NON_DEFAULT Serialized: {"date":"1970-01-01T00:00:00.000+0000"}

Therefore I think that using either of these in annotations will eventually lead to unexpected behavior of the code using the class.

I think that NON_EMPTY could be re-defined to be more useful (e.g. defining "empty" strings as typical utilities will do). And NON_DEFAULT is quite "dangerous" with the current behavior.

cowtowncoder commented 7 years ago

Ok. It sounds that serializer for Date is not implementing handling correctly.

Intent with 2.7 and above for NON_EMPTY is as documented by javadocs: so that values are written out, unless they are:

So any non-null Date should be serialized. If this is not being done it is a bug.

NON_DEFAULT is bit trickier because it has 2 somewhat different behaviors, depending on where annotation is:

  1. annotation is for POJO class
  2. annotation is for property (getter or field)

Case (1) is the complex one, and considers default values for properties of the POJO. So if Date value of property is the same as whatever is the default value wen instance of POJO was created with default constructor, it will be excluded.

Case (2) should work in such a way as to exclude everything that NON_EMPTY excludes, plus "default value"s of other types. This would mean that Date with internal timestamp of 0 should be excluded.

As the first step, I will check if I can reproduce the issue with NON_EMPTY; that should be easy to fix.

cowtowncoder commented 7 years ago

Ah, yes. You are right: handling is incorrect. isEmpty() exists for DateSerializer, and it should not. Worse, handling of default is more hard-coded (there is no isDefault() method in `JsonSerializer API).

Thank you for reporting this; I will try to figure out how to proceed wrt default value exclusion.

cowtowncoder commented 7 years ago

Since only annotation declarations are in this repo, issue belongs in jackson-databind, so re-created as:

https://github.com/FasterXML/jackson-databind/issues/1550