Skip to content

Commit 92223fe

Browse files
committed
Fixed FasterXML#18
1 parent e87a739 commit 92223fe

File tree

5 files changed

+132
-41
lines changed

5 files changed

+132
-41
lines changed

release-notes/CREDITS

+5
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ Jerome Loisel (jloisel@github)
1414

1515
* Reported #14: Missing `@type` when serializing Optional<Interface>
1616
(2.6.2)
17+
18+
James Lorenzen (jlorenzen@github)
19+
20+
* Requested #18: Allow use of `@JsonInclude(content=Include.NON_EMPTY)` with `Optional<>`
21+
(2.7.0)

release-notes/VERSION

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Project: jackson-datatype-jdk8
66

77
2.7.0 (not yet released)
88

9+
#18: Allow use of `@JsonInclude(content=Include.NON_EMPTY)` with `Optional<>`
10+
(requested by James L)
11+
912
2.6.3 (not yet released)
1013

1114
#13: Allow use of `@JsonDeserialize(contentAs=XXX)` with `Optional`

src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java

+96-29
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import java.io.IOException;
44
import java.util.Optional;
55

6+
import com.fasterxml.jackson.annotation.JsonInclude;
7+
68
import com.fasterxml.jackson.core.JsonGenerator;
9+
710
import com.fasterxml.jackson.databind.*;
811
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
912
import com.fasterxml.jackson.databind.introspect.Annotated;
@@ -35,14 +38,28 @@ public class OptionalSerializer
3538
*/
3639
protected final NameTransformer _unwrapper;
3740

41+
/**
42+
* Further guidance on serialization-inclusion (or not), regarding
43+
* contained value (if any).
44+
*
45+
* @since 2.7
46+
*/
47+
protected final JsonInclude.Include _contentInclusion;
48+
3849
/**
3950
* If element type can not be statically determined, mapping from
4051
* runtime type to serializer is handled using this object
4152
*
4253
* @since 2.6
4354
*/
4455
protected transient PropertySerializerMap _dynamicSerializers;
45-
56+
57+
/*
58+
/**********************************************************
59+
/* Constructors, factory methods
60+
/**********************************************************
61+
*/
62+
4663
public OptionalSerializer(JavaType type) {
4764
this(type, null);
4865
}
@@ -55,30 +72,57 @@ protected OptionalSerializer(JavaType optionalType, JsonSerializer<?> valueSer)
5572
_property = null;
5673
_valueSerializer = (JsonSerializer<Object>) valueSer;
5774
_unwrapper = null;
75+
_contentInclusion = null;
5876
_dynamicSerializers = PropertySerializerMap.emptyForProperties();
5977
}
6078

6179
@SuppressWarnings("unchecked")
6280
protected OptionalSerializer(OptionalSerializer base,
63-
BeanProperty property, JsonSerializer<?> valueSer, NameTransformer unwrapper)
81+
BeanProperty property, JsonSerializer<?> valueSer, NameTransformer unwrapper,
82+
JsonInclude.Include contentIncl)
6483
{
6584
super(base);
6685
_referredType = base._referredType;
6786
_dynamicSerializers = base._dynamicSerializers;
6887
_property = property;
6988
_valueSerializer = (JsonSerializer<Object>) valueSer;
7089
_unwrapper = unwrapper;
90+
if ((contentIncl == JsonInclude.Include.USE_DEFAULTS)
91+
|| (contentIncl == JsonInclude.Include.ALWAYS)) {
92+
_contentInclusion = null;
93+
} else {
94+
_contentInclusion = contentIncl;
95+
}
7196
}
7297

98+
@Override
99+
public JsonSerializer<Optional<?>> unwrappingSerializer(NameTransformer transformer) {
100+
JsonSerializer<Object> ser = _valueSerializer;
101+
if (ser != null) {
102+
ser = ser.unwrappingSerializer(transformer);
103+
}
104+
NameTransformer unwrapper = (_unwrapper == null) ? transformer
105+
: NameTransformer.chainedTransformer(transformer, _unwrapper);
106+
return withResolved(_property, ser, unwrapper, _contentInclusion);
107+
}
108+
73109
protected OptionalSerializer withResolved(BeanProperty prop,
74-
JsonSerializer<?> ser, NameTransformer unwrapper)
110+
JsonSerializer<?> ser, NameTransformer unwrapper,
111+
JsonInclude.Include contentIncl)
75112
{
76-
if ((_property == prop) && (_valueSerializer == ser) && (_unwrapper == unwrapper)) {
113+
if ((_property == prop) && (contentIncl == _contentInclusion)
114+
&& (_valueSerializer == ser) && (_unwrapper == unwrapper)) {
77115
return this;
78116
}
79-
return new OptionalSerializer(this, prop, ser, unwrapper);
117+
return new OptionalSerializer(this, prop, ser, unwrapper, contentIncl);
80118
}
81119

120+
/*
121+
/**********************************************************
122+
/* Contextualization (support for property annotations)
123+
/**********************************************************
124+
*/
125+
82126
@Override
83127
public JsonSerializer<?> createContextual(SerializerProvider provider,
84128
BeanProperty property) throws JsonMappingException
@@ -92,20 +136,38 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
92136
} else {
93137
ser = provider.handlePrimaryContextualization(ser, property);
94138
}
95-
return withResolved(property, ser, _unwrapper);
139+
// Also: may want to have more refined exclusion based on referenced value
140+
JsonInclude.Include contentIncl = _contentInclusion;
141+
if (property != null) {
142+
AnnotationIntrospector intr = provider.getAnnotationIntrospector();
143+
if (intr != null) {
144+
JsonInclude.Value incl = intr.findPropertyInclusion(property.getMember());
145+
if (incl != null) {
146+
JsonInclude.Include newIncl = incl.getContentInclusion();
147+
if ((newIncl != contentIncl) && (newIncl != JsonInclude.Include.NON_DEFAULT)) {
148+
contentIncl = newIncl;
149+
}
150+
}
151+
}
152+
}
153+
return withResolved(property, ser, _unwrapper, contentIncl);
96154
}
97155

98156
protected boolean _useStatic(SerializerProvider provider, BeanProperty property,
99157
JavaType referredType)
100158
{
101159
// First: no serializer for `Object.class`, must be dynamic
102-
if (_referredType.hasRawClass(Object.class)) {
160+
if (_referredType.isJavaLangObject()) {
103161
return false;
104162
}
105163
// but if type is final, might as well fetch
106164
if (_referredType.isFinal()) { // or should we allow annotation override? (only if requested...)
107165
return true;
108166
}
167+
// also: if indicated by typing, should be considered static
168+
if (_referredType.useStaticType()) {
169+
return true;
170+
}
109171
// if neither, maybe explicit annotation?
110172
AnnotationIntrospector intr = provider.getAnnotationIntrospector();
111173
if ((intr != null) && (property != null)) {
@@ -124,15 +186,36 @@ protected boolean _useStatic(SerializerProvider provider, BeanProperty property,
124186
return provider.isEnabled(MapperFeature.USE_STATIC_TYPING);
125187
}
126188

189+
/*
190+
/**********************************************************
191+
/* API overrides
192+
/**********************************************************
193+
*/
194+
127195
@Override
128-
public JsonSerializer<Optional<?>> unwrappingSerializer(NameTransformer transformer) {
196+
public boolean isEmpty(SerializerProvider provider, Optional<?> value)
197+
{
198+
if ((value == null) || !value.isPresent()) {
199+
return true;
200+
}
201+
if (_contentInclusion == null) {
202+
return false;
203+
}
204+
Object contents = value.get();
129205
JsonSerializer<Object> ser = _valueSerializer;
130-
if (ser != null) {
131-
ser = ser.unwrappingSerializer(transformer);
206+
if (ser == null) {
207+
try {
208+
ser = _findCachedSerializer(provider, value.getClass());
209+
} catch (JsonMappingException e) { // nasty but necessary
210+
throw new RuntimeJsonMappingException(e);
211+
}
132212
}
133-
NameTransformer unwrapper = (_unwrapper == null) ? transformer
134-
: NameTransformer.chainedTransformer(transformer, _unwrapper);
135-
return withResolved(_property, ser, unwrapper);
213+
return ser.isEmpty(provider, contents);
214+
}
215+
216+
@Override
217+
public boolean isUnwrappingSerializer() {
218+
return (_unwrapper != null);
136219
}
137220

138221
/*
@@ -174,22 +257,6 @@ public void serializeWithType(Optional<?> opt,
174257
}
175258
}
176259

177-
/*
178-
/**********************************************************
179-
/* API overrides
180-
/**********************************************************
181-
*/
182-
183-
@Override
184-
public boolean isEmpty(SerializerProvider provider, Optional<?> value) {
185-
return (value == null) || !value.isPresent();
186-
}
187-
188-
@Override
189-
public boolean isUnwrappingSerializer() {
190-
return (_unwrapper != null);
191-
}
192-
193260
/*
194261
/**********************************************************
195262
/* Introspection support

src/test/java/com/fasterxml/jackson/failing/OptionalBasicTest.java renamed to src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalnclusionTest.java

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fasterxml.jackson.failing;
1+
package com.fasterxml.jackson.datatype.jdk8;
22

33
import java.util.Optional;
44

@@ -11,7 +11,7 @@
1111

1212
import com.fasterxml.jackson.datatype.jdk8.ModuleTestBase;
1313

14-
public class OptionalBasicTest extends ModuleTestBase
14+
public class OptionalnclusionTest extends ModuleTestBase
1515
{
1616
@JsonAutoDetect(fieldVisibility=Visibility.ANY)
1717
public static final class OptionalData {
@@ -20,7 +20,7 @@ public static final class OptionalData {
2020

2121
// for [datatype-jdk8#18]
2222
static class OptionalNonEmptyStringBean {
23-
@JsonInclude(Include.NON_EMPTY)
23+
@JsonInclude(value=Include.NON_EMPTY, content=Include.NON_EMPTY)
2424
public Optional<String> value;
2525

2626
public OptionalNonEmptyStringBean() { }
@@ -31,15 +31,6 @@ public OptionalNonEmptyStringBean() { }
3131

3232
private final ObjectMapper MAPPER = mapperWithModule();
3333

34-
public void testSerOptNonDefault() throws Exception
35-
{
36-
OptionalData data = new OptionalData();
37-
data.myString = null;
38-
String value = mapperWithModule().setSerializationInclusion(
39-
JsonInclude.Include.NON_DEFAULT).writeValueAsString(data);
40-
assertEquals("{}", value);
41-
}
42-
4334
public void testSerOptNonEmpty() throws Exception
4435
{
4536
OptionalData data = new OptionalData();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.fasterxml.jackson.failing;
2+
3+
import java.util.Optional;
4+
5+
import com.fasterxml.jackson.annotation.*;
6+
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
7+
8+
import com.fasterxml.jackson.datatype.jdk8.ModuleTestBase;
9+
10+
public class OptionalnclusionTest extends ModuleTestBase
11+
{
12+
@JsonAutoDetect(fieldVisibility=Visibility.ANY)
13+
public static final class OptionalData {
14+
public Optional<String> myString = Optional.empty();
15+
}
16+
17+
public void testSerOptNonDefault() throws Exception
18+
{
19+
OptionalData data = new OptionalData();
20+
data.myString = null;
21+
String value = mapperWithModule().setSerializationInclusion(
22+
JsonInclude.Include.NON_DEFAULT).writeValueAsString(data);
23+
assertEquals("{}", value);
24+
}
25+
}

0 commit comments

Comments
 (0)