How to unit-test Jackson JsonSerializer and JsonDeserializer

I've written custom JsonSerializer and JsonDeserializer for my app. Now I want to write some unit-tests for them.

How should a clean test case look like?

Are there some clean examples out there?

(clean means no dependencies to other frameworks or libraries)


JsonSerializer

The example is serialising a LocalDateTime but this can replaced by the required type.

@Test
public void serialises_LocalDateTime() throws JsonProcessingException, IOException {
    Writer jsonWriter = new StringWriter();
    JsonGenerator jsonGenerator = new JsonFactory().createGenerator(jsonWriter);
    SerializerProvider serializerProvider = new ObjectMapper().getSerializerProvider();
    new LocalDateTimeJsonSerializer().serialize(LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0), jsonGenerator, serializerProvider);
    jsonGenerator.flush();
    assertThat(jsonWriter.toString(), is(equalTo("\"2000-01-01T00:00:00\"")));
}

JsonDeserializer

The example is deserialising a Number but this can replaced by the required type.

    private ObjectMapper mapper;
    private CustomerNumberDeserialiser deserializer;

    @Before
    public void setup() {
        mapper = new ObjectMapper();
        deserializer = new CustomerNumberDeserialiser();
    }

    @Test
    public void floating_point_string_deserialises_to_Double_value() {
        String json = String.format("{\"value\":%s}", "\"1.1\"");
        Number deserialisedNumber = deserialiseNumber(json);
        assertThat(deserialisedNumber, instanceOf(Double.class));
        assertThat(deserialisedNumber, is(equalTo(1.1d)));
    }

    @SneakyThrows({JsonParseException.class, IOException.class})
    private Number deserialiseNumber(String json) {
        InputStream stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
        JsonParser parser = mapper.getFactory().createParser(stream);
        DeserializationContext ctxt = mapper.getDeserializationContext();
        parser.nextToken();
        parser.nextToken();
        parser.nextToken();
        return deserializer.deserialize(parser, ctxt);
    }

UPDATE

When upgrading to jackson 2.9.3 I received a NullPointerException in DeserializationContext in isEnabled(MapperFeature feature) when deserialising strings to numbers because new ObjectMapper() initialises _config to null.

To get around this, I used this SO answer to spy on the final class DeserializationContext:

DeserializationContext ctxt = spy(mapper.getDeserializationContext());
doReturn(true).when(ctxt).isEnabled(any(MapperFeature.class));

I feel there must be a better way, so please comment if you have one.


Other answers have covered serialization very well. I have one other suggestion for deserialization.

If your domain class has the @JsonDeserialize annotation, then your deserializer will be picked up by the default ObjectMapper, without the need to explicitly register it.

@JsonDeserialize(using=ExampleDeserializer.class)
public class Example {
    private String name;
    private BananaStore bananaStore;

    public Example(String name, BananaStore bananaStore) {
        this.name = name;
        this.bananaStore = bananaStore;
    }

    public String getName() {
        return name;
    }

    public BananaStore getBananaStore() {
        return bananaStore;
    }
}

and your unit test can become much shorter:

public class ExampleDeserializerTest {

    @Test
    public void deserialize() throws IOException {
        String json = "{\"name\":\"joe\",\"bananas\":16}";

        Example example = new ObjectMapper().readValue(json, Example.class);

        assertEquals("joe", example.getName());
        assertEquals(16, example.getBananaStore().getBananaCount());
    }
}

A deserializer can be unit tested like this:

public class CustomFooDeserializerTest {
    
    private Foo foo;
    
    @Before
    public void setUp() {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Foo.class, new CustomFooDeserializer());
        objectMapper.registerModule(module);
        foo = objectMapper.readValue(new File("path/to/file"), Foo.class);
    }
    
    @Test
    public void shouldSetSomeProperty() {
        assertThat(foo.getSomeProperty(), is("valueOfSomeProperty"));
    }
    
}