2

I have a basic ObjectMapper configured with a custom serializer and deserializer for a property in my Object. I would like to capture the latency for serialization and deserialization of my entire Object using a Micrometer Timer meter.

I can add a custom serializer and deserializer at the top Object level, instead of just the property that I have it for, and pass in the Timer to capture metrics. But, this would entail parsing the entire Object explicitly for the sole purpose of capturing metrics. I am hoping to get feedback on whether this is the only option or if there is a better way to achieve this.

The Object I am working with

public class Person {

  String name;
  int age;
  Address address
}

The property I have custom serializer and deserializer for

public class Address {

  String addLineOne;
  String addLineTwo;
  int zipCode;
}

ObjectMapper configured

@Bean
public ObjectMapper objectMapper() {
  ObjectMapper objectMapper = new ObjectMapper();

  objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
  objectMapper.configure(Deserialization.FAIL_ON_UNKNOWN_PROPERTIES, false);

  SimpleModule module = new SimpleModule();
  module.addDeserializer(Address.class, new AddressDeserializer());
  module.addSerializer(Address.class, new AddressSerializer());
  objectMapper.registerModule(module);

  return objectMapper;
}
frpet
  • 155
  • 1
  • 9
  • If you are interested in timing the operations on the objectmapper, hence timing serialization, why not inherit from the ObjectMapper and add timing information in overridden methods? – Gerben Jongerius Aug 09 '19 at 10:54

1 Answers1

1

For POJO deserialisation Jackson by default uses com.fasterxml.jackson.databind.deser.BeanDeserializer class. We can extend it and register by using com.fasterxml.jackson.databind.deser.BeanDeserializerModifier class. Below example shows the idea:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.IOException;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = objectMapper();
        String json = mapper.writeValueAsString(new Person());

        for (int i = 0; i < 10; i++) {
            mapper.readValue(json, Person.class);
        }
    }

    public static ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                if (deserializer instanceof BeanDeserializer && beanDesc.getBeanClass() == Person.class) {
                    return new TimerBeanDeserializer((BeanDeserializerBase) deserializer);
                }
                return super.modifyDeserializer(config, beanDesc, deserializer);
            }
        });
        module.addDeserializer(Address.class, new AddressDeserializer());
        module.addSerializer(Address.class, new AddressSerializer());
        objectMapper.registerModule(module);

        return objectMapper;
    }
}

class TimerBeanDeserializer extends BeanDeserializer {

    private Timer timer = new Timer();

    protected TimerBeanDeserializer(BeanDeserializerBase src) {
        super(src);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        timer.start();
        Object res = super.deserialize(p, ctxt);
        System.out.println("Deserialization took: " + timer.end() + " nanos");

        return res;
    }
}

class Timer {
    private long start;

    public void start() {
        this.start = System.nanoTime();
    }

    public long end() {
        return System.nanoTime() - start;
    }
}

Above code prints:

Deserialization took: 660853 nanos
Deserialization took: 48276 nanos
Deserialization took: 43741 nanos
Deserialization took: 44786 nanos
Deserialization took: 39230 nanos
Deserialization took: 39917 nanos
Deserialization took: 39745 nanos
Deserialization took: 38330 nanos
Deserialization took: 38994 nanos
Deserialization took: 38717 nanos

See also:

  1. Jackson custom serialization and deserialization
  2. @Valid when creating objects with jackson without controller
  3. Can Jackson check for duplicated properties in a case insensitive way?
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146