简体   繁体   中英

Unexpected serialization behavior with configured Jackson ObjectMapper

I am using Jackson 2.10.5 to serialize the same java.util.Date object three times.

  1. The first time, with a basic Jackson ObjectMapper . I see the timestamp.
  2. Then I configure the same ObjectMapper , and rewrite. I get the same result.
  3. Then I construct a new ObjectMapper , configure it the same way. I get a different result, the class name and the timestamp in a JSON list.

The configuration is intended to tell the ObjectMapper to include the class name of every object except java.util.Date as a JSON property.

So I have two questions. The first is why is the date object serialized differently in the third case? Any advice on using the PolymorphicTypeMapper differently would be appreciated.

The second is why the first and second are the same (is it because the Object mapper has a cache (ouch!)?).

[EDIT: I should have mentioned that the use case for this is as a JSON provider for Jersey. I have a way of generating and configuring an ObjectMapper at launch time, but the (in)ability to configure per-write is just for the test code above.]

private PolymorphicTypeValidator getPTV() {
    return BasicPolymorphicTypeValidator.builder()
            .denyForExactBaseType(Date.class)
            .build();
}


@Test
public void serializationTest() {
    try {
        Date now = new Date();

        // Create an object mapper and serialize the date
        ObjectMapper om = new ObjectMapper();
        String serialized1 = om.writeValueAsString(now); // result: 1605744866827

        om.activateDefaultTypingAsProperty(getPTV(), ObjectMapper.DefaultTyping.EVERYTHING, "@class");
        String serialized2 = om.writeValueAsString(now); // result: 1605744866827

        ObjectMapper om2 = new ObjectMapper();
        om2.activateDefaultTypingAsProperty(getPTV(), ObjectMapper.DefaultTyping.EVERYTHING, "@class");
        String serialized3 = om2.writeValueAsString(now); // result: ["java.util.Date",1605744866827]

        Logger.getLogger(SerializationTest.class).info(serialized1);
        Logger.getLogger(SerializationTest.class).info(serialized2);
        Logger.getLogger(SerializationTest.class).info(serialized3);

        Assert.assertEquals("Unexpected change in serialization 1-2", serialized1, serialized2);
        Assert.assertEquals("Unexpected change in serialization 1-3", serialized1, serialized3);

    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}

Here's the output:

INFO  2020-11-18 16:14:27,065 [main] <> test.SerializationTest : 1605744866827
INFO  2020-11-18 16:14:27,066 [main] <> test.SerializationTest : 1605744866827
INFO  2020-11-18 16:14:27,066 [main] <> test.SerializationTest : ["java.util.Date",1605744866827]

org.junit.ComparisonFailure: Unexpected change in serialization 1-3 
Expected :1605744866827
Actual   :["java.util.Date",1605744866827]

Read the documentation , ie the javadoc of ObjectMapper , which says ( bold highlights by me) :

Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls . If configuration of a mapper is modified after first usage, changes may or may not take effect , and configuration calls themselves may fail . If you need to use different configuration, you have two main possibilities:

  • Construct and use ObjectReader for reading, ObjectWriter for writing. Both types are fully immutable and you can freely create new instances with different configuration using either factory methods of ObjectMapper , or readers/writers themselves. Construction of new ObjectReader s and ObjectWriter s is a very light-weight operation so it is usually appropriate to create these on per-call basis, as needed, for configuring things like optional indentation of JSON.

  • If the specific kind of configurability is not available via ObjectReader and ObjectWriter , you may need to use multiple ObjectMapper instead (for example: you can not change mix-in annotations on-the-fly; or, set of custom (de)serializers). To help with this usage, you may want to use method copy() which creates a clone of the mapper with specific configuration, and allows configuration of the copied instance before it gets used. Note that copy() operation is as expensive as constructing a new ObjectMapper instance: if possible, you should still pool and reuse mappers if you intend to use them for multiple operations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM