// ============================================================ objectMapper.setSerializerFactory(new CompactBeanSerializerFactory(objectMapper)); // ============================================================ /** Parses an empty object using readValue("{}", type). */ private static Object defaultInstance(Class type, ObjectMapper objectMapper) { try { return objectMapper.readValue(EMPTY_OBJECT_JSON, type); // EMPTY_OBJECT_JSON == "{}", set in a static initializer by getting // a JsonGenerator and calling writeStartObject and writeEndObject. } catch (IOException e) { // hmm, shouldn't readValue(String,...) throw only JsonProcessingExceptions, not general IOE? throw new RuntimeException("cannot parse " + EMPTY_OBJECT_JSON + " as " + type, e); } } // ============================================================ class CompactBeanSerializerFactory extends CustomSerializerFactory { private final ObjectMapper mapper; CompactBeanSerializerFactory(ObjectMapper mapper) { this.mapper = mapper; } @Override public JsonSerializer findBeanSerializer(Class type, SerializationConfig config) { // code for caching already created serializers omitted JsonSerializer serializer = super.findBeanSerializer(type, config); return serializer instanceof BeanSerializer ? new CompactBeanSerializer(type, mapper, (BeanSerializer) serializer, findBeanProperties(config, (BasicBeanDescription) config.introspect(type)) ) : serializer; } } // ============================================================ class CompactBeanSerializer extends JsonSerializer { private final Class type; private final BeanSerializer beanSerializer; private final Collection properties; private final ObjectMapper objectMapper; CompactBeanSerializer(Class type, ObjectMapper objectMapper, BeanSerializer beanSerializer, Collection properties) { /* set all fields */ } @Override public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) { try { // -- build collection of initialized properties List setProperties = new ArrayList(properties.size()); JsonGenerator nullGen = objectMapper.getJsonFactory().createJsonGenerator(NullWriter.INSTANCE); nullGen.writeStartObject(); StoringSerializerProvider storingSP = new StoringSerializerProvider(provider); Object defaultValuesBean = defaultInstance(bean.getClass(), objectMapper); for (BeanPropertyWriter prop : properties) { prop.serializeAsField(defaultValuesBean, nullGen, storingSP); Object defaultValue = storingSP.getAndResetLastValue(); prop.serializeAsField(bean, nullGen, storingSP); Object value = storingSP.getAndResetLastValue(); boolean equals = defaultValue == value || (defaultValue != null && defaultValue.equals(value)); if (!equals) { setProperties.add(prop); } } // -- serialize only changed properties if (setProperties.size() != properties.size()) { if (setProperties.isEmpty()) { jgen.writeStartObject(); jgen.writeEndObject(); } else { new BeanSerializer(bean.getClass(), setProperties).serialize(bean, jgen, provider); } return; } } catch (Exception e) { // OK, invoke default impl logger.warn("failed to determine which properties are set for " + bean, e); } beanSerializer.serialize(bean, jgen, provider); } } // ============================================================ /** A null device which does nothing. */ class NullWriter extends Writer { ... } // ============================================================ /** Decorates a given SerializerProvider to store the most recently serialized value. */ class StoringSerializerProvider extends SerializerProvider { private final SerializerProvider decoratee; private Object lastValue; StoringSerializerProvider(SerializerProvider decoratee) { super(decoratee.getConfig()); this.decoratee = decoratee; } Object getAndResetLastValue() { Object v = lastValue; lastValue = null; return v; } @Override public JsonSerializer findValueSerializer(Class type) { final JsonSerializer defaultSerializer = decoratee.findValueSerializer(type); return new JsonSerializer() { @Override public void serialize(Object value, JsonGenerator g, SerializerProvider p) { lastValue = value; defaultSerializer.serialize(value, g, p); } }; } @Override public void defaultSerializeDateValue(long timestamp, JsonGenerator g) { lastValue = timestamp; decoratee.defaultSerializeDateValue(timestamp, g); } // Also set lastValue in defaultSerializeDateValue(Date, JsonGenerator) and // serializeValue(SerializationConfig, JsonGenerator, Object, SerializerFactory) // Other methods just delegate to decoratee. }