From a4a0fb437315949c2771eeaa5251ed1dbd80a079 Mon Sep 17 00:00:00 2001 From: Henning Schmiedehausen Date: Fri, 5 Jun 2015 16:02:05 -0700 Subject: [PATCH] Add configureAbsentsAsNulls config setting This aligns this feature with the Guava module. However, the default is 'false' as compared to the Guava 'true' for backwards compatibility. --- .../jdk8/Jdk8BeanSerializerModifier.java | 30 ++++++++++ .../jackson/datatype/jdk8/Jdk8Module.java | 38 ++++++++++++ .../jdk8/Jdk8OptionalBeanPropertyWriter.java | 27 +++++++++ .../jdk8/TestConfigureAbsentsAsNulls.java | 58 +++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8BeanSerializerModifier.java create mode 100644 src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8OptionalBeanPropertyWriter.java create mode 100644 src/test/java/com/fasterxml/jackson/datatype/jdk8/TestConfigureAbsentsAsNulls.java diff --git a/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8BeanSerializerModifier.java b/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8BeanSerializerModifier.java new file mode 100644 index 0000000..a279cc2 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8BeanSerializerModifier.java @@ -0,0 +1,30 @@ +package com.fasterxml.jackson.datatype.jdk8; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; + +import java.util.List; +import java.util.Optional; + +/** + * {@link BeanSerializerModifier} needed to sneak in handler to exclude "absent" + * optional values iff handling of "absent as nulls" is enabled. + */ +public class Jdk8BeanSerializerModifier extends BeanSerializerModifier +{ + @Override + public List changeProperties(SerializationConfig config, + BeanDescription beanDesc, + List beanProperties) + { + for (int i = 0; i < beanProperties.size(); ++i) { + final BeanPropertyWriter writer = beanProperties.get(i); + if (Optional.class.isAssignableFrom(writer.getPropertyType())) { + beanProperties.set(i, new Jdk8OptionalBeanPropertyWriter(writer)); + } + } + return beanProperties; + } +} diff --git a/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8Module.java b/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8Module.java index db1dcd6..6f04ed5 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8Module.java +++ b/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8Module.java @@ -5,12 +5,32 @@ public class Jdk8Module extends Module { + /** + * Configuration setting that determines whether `Optional.empty()` is + * considered "same as null" for serialization purposes; that is, to be + * filtered same as nulls are. + * If enabled, absent values are treated like nulls; if disabled, they are not. + * In either case, absent values are always considered "empty". + *

+ * Default value is `false` for backwards compatibility (2.5 and prior + * only had this behavior). + *

+ * Note that this setting MUST be changed BEFORE registering the module: + * changes after registration will have no effect. + */ + protected boolean _cfgHandleAbsentAsNull = false; + @Override public void setupModule(SetupContext context) { context.addSerializers(new Jdk8Serializers()); context.addDeserializers(new Jdk8Deserializers()); // And to fully support Optionals, need to modify type info: context.addTypeModifier(new Jdk8TypeModifier()); + + // Allow enabling "treat Optional.empty() like Java nulls" + if (_cfgHandleAbsentAsNull) { + context.addBeanSerializerModifier(new Jdk8BeanSerializerModifier()); + } } @Override @@ -18,6 +38,24 @@ public Version version() { return PackageVersion.VERSION; } + /** + * Configuration method that may be used to change configuration setting + * {@link #_cfgHandleAbsentAsNull}: enabling means that `Optional.empty()` values + * are handled like Java nulls (wrt filtering on serialization); disabling that + * they are only treated as "empty" values, but not like native Java nulls. + * Recommended setting for this value is `false`. For compatibility with older versions + * of other "optional" values (like Guava optionals), it can be set to 'true'. The + * default is `false` for backwards compatibility. + * + * @return This module instance, useful for chaining calls + * + * @since 2.6 + */ + public Jdk8Module configureAbsentsAsNulls(boolean state) { + _cfgHandleAbsentAsNull = state; + return this; + } + @Override public int hashCode() { return getClass().hashCode(); diff --git a/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8OptionalBeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8OptionalBeanPropertyWriter.java new file mode 100644 index 0000000..92ca261 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/datatype/jdk8/Jdk8OptionalBeanPropertyWriter.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.datatype.jdk8; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; + +import java.util.Optional; + +public class Jdk8OptionalBeanPropertyWriter extends BeanPropertyWriter { + + protected Jdk8OptionalBeanPropertyWriter(BeanPropertyWriter base) { + super(base); + } + + @Override + public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov) throws Exception + { + if (_nullSerializer == null) { + Object value = get(bean); + if (value == null || Optional.empty().equals(value)) { + return; + } + } + super.serializeAsField(bean, jgen, prov); + } + +} diff --git a/src/test/java/com/fasterxml/jackson/datatype/jdk8/TestConfigureAbsentsAsNulls.java b/src/test/java/com/fasterxml/jackson/datatype/jdk8/TestConfigureAbsentsAsNulls.java new file mode 100644 index 0000000..0129444 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/datatype/jdk8/TestConfigureAbsentsAsNulls.java @@ -0,0 +1,58 @@ +package com.fasterxml.jackson.datatype.jdk8; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class TestConfigureAbsentsAsNulls extends ModuleTestBase +{ + @JsonAutoDetect(fieldVisibility=Visibility.ANY) + public static final class OptionalData { + public Optional myString = Optional.empty(); + } + + /* + /********************************************************************** + /* Test methods + /********************************************************************** + */ + + public void testConfigAbsentsAsNullsTrue() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(true)); + + OptionalData data = new OptionalData(); + String value = mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data); + assertEquals("{}", value); + } + + public void testConfigAbsentsAsNullsFalse() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(false)); + + OptionalData data = new OptionalData(); + String value = mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data); + assertEquals("{\"myString\":null}", value); + } + + public void testConfigNonAbsentAbsentsAsNullsTrue() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(true)); + + OptionalData data = new OptionalData(); + String value = mapper.setSerializationInclusion(JsonInclude.Include.NON_ABSENT).writeValueAsString(data); + assertEquals("{}", value); + } + + public void testConfigNonAbsentAbsentsAsNullsFalse() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(false)); + + OptionalData data = new OptionalData(); + String value = mapper.setSerializationInclusion(JsonInclude.Include.NON_ABSENT).writeValueAsString(data); + assertEquals("{}", value); + } +}