Skip to content
This repository was archived by the owner on Jan 20, 2025. It is now read-only.

Commit 0b3cbb7

Browse files
committed
Trying to clean up, improve handling of Guava Optional type, wrt polymorphic types
1 parent d367057 commit 0b3cbb7

File tree

6 files changed

+147
-16
lines changed

6 files changed

+147
-16
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ com.fasterxml.jackson.core.util,
3838
com.fasterxml.jackson.databind,
3939
com.fasterxml.jackson.databind.deser,
4040
com.fasterxml.jackson.databind.deser.std,
41+
com.fasterxml.jackson.databind.introspect,
4142
com.fasterxml.jackson.databind.jsontype,
4243
com.fasterxml.jackson.databind.ser,
4344
com.fasterxml.jackson.databind.ser.std,

src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
import com.fasterxml.jackson.databind.BeanDescription;
55
import com.fasterxml.jackson.databind.DeserializationConfig;
6+
import com.fasterxml.jackson.databind.DeserializationContext;
67
import com.fasterxml.jackson.databind.JavaType;
78
import com.fasterxml.jackson.databind.JsonDeserializer;
89
import com.fasterxml.jackson.databind.JsonMappingException;
910
import com.fasterxml.jackson.databind.KeyDeserializer;
1011
import com.fasterxml.jackson.databind.deser.Deserializers;
12+
import com.fasterxml.jackson.databind.introspect.Annotated;
1113
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
1214
import com.fasterxml.jackson.databind.type.CollectionType;
1315
import com.fasterxml.jackson.databind.type.MapLikeType;
@@ -261,10 +263,24 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
261263
public JsonDeserializer<?> findBeanDeserializer(final JavaType type, DeserializationConfig config,
262264
BeanDescription beanDesc) throws JsonMappingException {
263265
Class<?> raw = type.getRawClass();
264-
if(Optional.class.isAssignableFrom(raw)){
265-
return new GuavaOptionalDeserializer(type);
266+
if (Optional.class.isAssignableFrom(raw)){
267+
JsonDeserializer<?> valueDeser = type.getValueHandler();
268+
TypeDeserializer typeDeser = type.getTypeHandler();
269+
return new GuavaOptionalDeserializer(type, typeDeser, valueDeser);
266270
}
267271
return super.findBeanDeserializer(type, config, beanDesc);
268272
}
269273

274+
// Copied from jackson-databind's "BasicDeserializerFactory":
275+
protected JsonDeserializer<Object> findDeserializerFromAnnotation(DeserializationContext ctxt,
276+
Annotated ann)
277+
throws JsonMappingException
278+
{
279+
Object deserDef = ctxt.getAnnotationIntrospector().findDeserializer(ann);
280+
if (deserDef == null) {
281+
return null;
282+
}
283+
return ctxt.deserializerInstance(ann, deserDef);
284+
}
285+
270286
}

src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,114 @@
44

55
import com.fasterxml.jackson.core.JsonParser;
66
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.core.JsonToken;
8+
import com.fasterxml.jackson.databind.BeanProperty;
79
import com.fasterxml.jackson.databind.DeserializationContext;
810
import com.fasterxml.jackson.databind.JavaType;
11+
import com.fasterxml.jackson.databind.JsonDeserializer;
12+
import com.fasterxml.jackson.databind.JsonMappingException;
13+
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
914
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
1015
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
1116
import com.google.common.base.Optional;
1217

13-
public final class GuavaOptionalDeserializer extends StdDeserializer<Optional<?>>
18+
public class GuavaOptionalDeserializer
19+
extends StdDeserializer<Optional<?>>
20+
implements ContextualDeserializer
1421
{
1522
private static final long serialVersionUID = 1L;
1623

17-
private final JavaType _referenceType;
24+
protected final JavaType _referenceType;
1825

26+
protected final JsonDeserializer<?> _valueDeserializer;
27+
28+
protected final TypeDeserializer _valueTypeDeserializer;
29+
30+
@Deprecated // since 2.3,
1931
public GuavaOptionalDeserializer(JavaType valueType) {
32+
this(valueType, null, null);
33+
}
34+
35+
public GuavaOptionalDeserializer(JavaType valueType,
36+
TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser)
37+
{
2038
super(valueType);
2139
_referenceType = valueType.containedType(0);
40+
_valueTypeDeserializer = typeDeser;
41+
_valueDeserializer = valueDeser;
2242
}
2343

2444
@Override
2545
public Optional<?> getNullValue() {
2646
return Optional.absent();
2747
}
2848

49+
/**
50+
* Overridable fluent factory method used for creating contextual
51+
* instances.
52+
*/
53+
public GuavaOptionalDeserializer withResolved(
54+
TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser)
55+
{
56+
return new GuavaOptionalDeserializer(_referenceType,
57+
typeDeser, valueDeser);
58+
}
59+
60+
/*
61+
/**********************************************************
62+
/* Validation, post-processing
63+
/**********************************************************
64+
*/
65+
66+
/**
67+
* Method called to finalize setup of this deserializer,
68+
* after deserializer itself has been registered. This
69+
* is needed to handle recursive and transitive dependencies.
70+
*/
71+
@Override
72+
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
73+
BeanProperty property) throws JsonMappingException
74+
{
75+
JsonDeserializer<?> deser = _valueDeserializer;
76+
TypeDeserializer typeDeser = _valueTypeDeserializer;
77+
if (deser == null) {
78+
deser = ctxt.findContextualValueDeserializer(_referenceType, property);
79+
}
80+
if (typeDeser != null) {
81+
typeDeser = typeDeser.forProperty(property);
82+
}
83+
if (deser == _valueDeserializer && typeDeser == _valueTypeDeserializer) {
84+
return this;
85+
}
86+
return withResolved(typeDeser, deser);
87+
}
88+
2989
@Override
3090
public Optional<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
31-
JsonProcessingException {
32-
Object reference = ctxt.findRootValueDeserializer(_referenceType).deserialize(jp, ctxt);
91+
JsonProcessingException
92+
{
93+
Object reference = _valueDeserializer.deserialize(jp, ctxt);
3394
return Optional.of(reference);
3495
}
3596

3697
@Override
3798
public Optional<?> deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
38-
throws IOException, JsonProcessingException {
39-
return deserialize(jp, ctxt);
99+
throws IOException, JsonProcessingException
100+
{
101+
final JsonToken t = jp.getCurrentToken();
102+
if (t == JsonToken.VALUE_NULL) {
103+
return getNullValue();
104+
}
105+
/* 03-Nov-2013, tatu: This gets rather tricky with "natural" types
106+
* (String, Integer, Boolean), which do NOT include type information.
107+
* These might actually be handled ok except that nominal type here
108+
* is `Optional`, so special handling is not invoked; instead, need
109+
* to do a work-around here.
110+
*/
111+
if (t != null && t.isScalarValue()) {
112+
return deserialize(jp, ctxt);
113+
}
114+
Object ref = _valueTypeDeserializer.deserializeTypedFromAny(jp, ctxt);
115+
return Optional.of(ref);
40116
}
41117
}

src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
116116
return (_createContextual(type, kd, etd, ed, creatorMethod));
117117
}
118118

119-
protected abstract JsonDeserializer<?> _createContextual(MapLikeType type,
120-
KeyDeserializer keyDeserializer, TypeDeserializer typeDeserializer,
121-
JsonDeserializer<?> elementDeserializer, Method method);
119+
protected abstract JsonDeserializer<?> _createContextual(MapLikeType t,
120+
KeyDeserializer kd, TypeDeserializer typeDeserializer,
121+
JsonDeserializer<?> ed, Method method);
122122

123123
@Override
124124
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,

src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public Holder(Object v) {
3030
value = v;
3131
}
3232
}
33-
33+
3434
/*
3535
/**********************************************************************
3636
/* Unit tests for verifying handling in absence of module registration

src/test/java/com/fasterxml/jackson/datatype/guava/TestOptional.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.fasterxml.jackson.datatype.guava;
22

3-
import com.fasterxml.jackson.annotation.JsonAutoDetect;
3+
import com.fasterxml.jackson.annotation.*;
44
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
5-
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
6+
67
import com.fasterxml.jackson.core.type.TypeReference;
8+
79
import com.fasterxml.jackson.databind.ObjectMapper;
10+
811
import com.google.common.base.Optional;
912

1013
public class TestOptional extends BaseTest
@@ -21,6 +24,26 @@ public static final class OptionalGenericData<T>{
2124
private Optional<T> myData;
2225
}
2326

27+
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class)
28+
public static class Unit
29+
{
30+
// @JsonIdentityReference(alwaysAsId=true)
31+
public Optional<Unit> baseUnit;
32+
33+
public Unit() { }
34+
public Unit(Optional<Unit> u) { baseUnit = u; }
35+
36+
public void link(Unit u) {
37+
baseUnit = Optional.of(u);
38+
}
39+
}
40+
41+
/*
42+
/**********************************************************************
43+
/* Test methods
44+
/**********************************************************************
45+
*/
46+
2447
public void testDeserAbsent() throws Exception {
2548
Optional<?> value = MAPPER.readValue("null", new TypeReference<Optional<String>>() {});
2649
assertFalse(value.isPresent());
@@ -99,17 +122,32 @@ public void testSerOptNull() throws Exception {
99122
assertEquals("{}", value);
100123
}
101124

102-
public void testWithTypingEnabled() throws Exception {
125+
public void testWithTypingEnabled() throws Exception
126+
{
103127
final ObjectMapper objectMapper = mapperWithModule();
104128
// ENABLE TYPING
105129
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
106130

107131
final OptionalData myData = new OptionalData();
108-
myData.myString = Optional.fromNullable("");
132+
myData.myString = Optional.fromNullable("abc");
109133

110134
final String json = objectMapper.writeValueAsString(myData);
111135

112136
final OptionalData deserializedMyData = objectMapper.readValue(json, OptionalData.class);
113137
assertEquals(myData.myString, deserializedMyData.myString);
114138
}
139+
140+
// for [Issue#17]
141+
public void testObjectId() throws Exception
142+
{
143+
final Unit input = new Unit();
144+
input.link(input);
145+
String json = MAPPER.writeValueAsString(input);
146+
Unit result = MAPPER.readValue(json, Unit.class);
147+
assertNotNull(result);
148+
assertNotNull(result.baseUnit);
149+
assertTrue(result.baseUnit.isPresent());
150+
Unit base = result.baseUnit.get();
151+
assertSame(result, base);
152+
}
115153
}

0 commit comments

Comments
 (0)