| // ============================================================ |
| 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<Object> findBeanSerializer(Class<?> type, SerializationConfig config) { |
| // code for caching already created serializers omitted |
| |
| JsonSerializer<Object> 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<Object> { |
| private final Class<?> type; |
| private final BeanSerializer beanSerializer; |
| private final Collection<? extends BeanPropertyWriter> properties; |
| private final ObjectMapper objectMapper; |
| |
| CompactBeanSerializer(Class<?> type, ObjectMapper objectMapper, BeanSerializer beanSerializer, |
| Collection<? extends BeanPropertyWriter> properties) { /* set all fields */ } |
| |
| @Override public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) { |
| try { |
| // -- build collection of initialized properties |
| List<BeanPropertyWriter> setProperties = new ArrayList<BeanPropertyWriter>(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<Object> findValueSerializer(Class<?> type) { |
| final JsonSerializer<Object> defaultSerializer = decoratee.findValueSerializer(type); |
| |
| return new JsonSerializer<Object>() { |
| @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. |
| } |
| |