Skip to content

Commit a1bbc87

Browse files
authored
[Guava] Suppport simple deserialization of Cache (#116)
1 parent 2a8bd8c commit a1bbc87

File tree

4 files changed

+432
-0
lines changed

4 files changed

+432
-0
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.fasterxml.jackson.datatype.guava;
22

33
import com.google.common.base.Optional;
4+
import com.google.common.cache.Cache;
5+
import com.google.common.cache.ForwardingCache;
6+
import com.google.common.cache.LoadingCache;
47
import com.google.common.collect.*;
58
import com.google.common.hash.HashCode;
69
import com.google.common.net.HostAndPort;
@@ -260,10 +263,42 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
260263
if (Table.class.isAssignableFrom(raw)) {
261264
// !!! TODO
262265
}
266+
// @since 2.16 : support Cache deserialization
267+
java.util.Optional<JsonDeserializer<?>> cacheDeserializer = findCacheDeserializer(raw, type, config,
268+
beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer);
269+
if (cacheDeserializer.isPresent()) {
270+
return cacheDeserializer.get();
271+
}
263272

264273
return null;
265274
}
266275

276+
/**
277+
* Find matching implementation of {@link Cache} deserializers by checking
278+
* if the parameter {@code raw} type is assignable.
279+
*
280+
* NOTE: Make sure the cache implementations are checked in such a way that more concrete classes are
281+
* compared first before more abstract ones.
282+
*
283+
* @return An optional {@link JsonDeserializer} for the cache type, if found.
284+
* @since 2.16
285+
*/
286+
private java.util.Optional<JsonDeserializer<?>> findCacheDeserializer(Class<?> raw, MapLikeType type,
287+
DeserializationConfig config, BeanDescription beanDesc, KeyDeserializer keyDeserializer,
288+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
289+
{
290+
/* // Example implementations
291+
if (LoadingCache.class.isAssignableFrom(raw)) {
292+
return ....your implementation....;
293+
}
294+
*/
295+
if (Cache.class.isAssignableFrom(raw)) {
296+
return java.util.Optional.of(
297+
new SimpleCacheDeserializer(type, keyDeserializer, elementTypeDeserializer, elementDeserializer));
298+
}
299+
return java.util.Optional.empty();
300+
}
301+
267302
@Override // since 2.7
268303
public JsonDeserializer<?> findReferenceDeserializer(ReferenceType refType,
269304
DeserializationConfig config, BeanDescription beanDesc,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.fasterxml.jackson.datatype.guava;
2+
3+
import com.fasterxml.jackson.databind.JsonDeserializer;
4+
import com.fasterxml.jackson.databind.KeyDeserializer;
5+
import com.fasterxml.jackson.databind.deser.NullValueProvider;
6+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
7+
import com.fasterxml.jackson.databind.type.MapLikeType;
8+
import com.fasterxml.jackson.datatype.guava.deser.cache.GuavaCacheDeserializer;
9+
import com.google.common.cache.Cache;
10+
import com.google.common.cache.CacheBuilder;
11+
12+
/**
13+
* {@link GuavaCacheDeserializer} class implementation for deserializing Guava {@link Cache} instances.
14+
*
15+
* @since 2.16
16+
*/
17+
public class SimpleCacheDeserializer
18+
extends GuavaCacheDeserializer<Cache<Object,Object>>
19+
{
20+
21+
/*
22+
/**********************************************************
23+
/* Life-cycle
24+
/**********************************************************
25+
*/
26+
27+
public SimpleCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
28+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
29+
{
30+
super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
31+
}
32+
33+
public SimpleCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
34+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
35+
NullValueProvider nvp)
36+
{
37+
super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, nvp);
38+
}
39+
40+
/*
41+
/**********************************************************************
42+
/* Abstract method overrides
43+
/**********************************************************************
44+
*/
45+
46+
@Override
47+
protected Cache<Object, Object> createCache() {
48+
return CacheBuilder.newBuilder().build();
49+
}
50+
51+
@Override
52+
protected JsonDeserializer<?> _createContextual(MapLikeType t,
53+
KeyDeserializer kd, TypeDeserializer vtd, JsonDeserializer<?> vd, NullValueProvider np)
54+
{
55+
return new SimpleCacheDeserializer(t, kd, vtd, vd, np);
56+
}
57+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package com.fasterxml.jackson.datatype.guava.deser.cache;
2+
3+
import com.fasterxml.jackson.core.JsonParser;
4+
import com.fasterxml.jackson.core.JsonToken;
5+
import com.fasterxml.jackson.databind.*;
6+
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
7+
import com.fasterxml.jackson.databind.deser.NullValueProvider;
8+
import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
9+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
10+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
11+
import com.fasterxml.jackson.databind.type.LogicalType;
12+
import com.fasterxml.jackson.databind.type.MapLikeType;
13+
import com.google.common.cache.Cache;
14+
15+
import java.io.IOException;
16+
import java.lang.reflect.InvocationTargetException;
17+
18+
public abstract class GuavaCacheDeserializer<T extends Cache<Object, Object>>
19+
extends StdDeserializer<T> implements ContextualDeserializer
20+
{
21+
private static final long serialVersionUID = 1L;
22+
23+
private final MapLikeType type;
24+
private final KeyDeserializer keyDeserializer;
25+
private final TypeDeserializer elementTypeDeserializer;
26+
private final JsonDeserializer<?> elementDeserializer;
27+
28+
/*
29+
* @since 2.16 : in 3.x demote to `ContainerDeserializerBase`
30+
*/
31+
private final NullValueProvider nullProvider;
32+
private final boolean skipNullValues;
33+
34+
/*
35+
/**********************************************************
36+
/* Life-cycle
37+
/**********************************************************
38+
*/
39+
40+
public GuavaCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
41+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
42+
this(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, null);
43+
}
44+
45+
public GuavaCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
46+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
47+
NullValueProvider nvp)
48+
{
49+
super(type);
50+
this.type = type;
51+
this.keyDeserializer = keyDeserializer;
52+
this.elementTypeDeserializer = elementTypeDeserializer;
53+
this.elementDeserializer = elementDeserializer;
54+
this.nullProvider = nvp;
55+
skipNullValues = (nvp == null) ? false : NullsConstantProvider.isSkipper(nvp);
56+
}
57+
58+
/*
59+
/**********************************************************
60+
/* Post-processing (contextualization)
61+
/**********************************************************
62+
*/
63+
64+
@Override
65+
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
66+
BeanProperty property) throws JsonMappingException
67+
{
68+
KeyDeserializer kd = keyDeserializer;
69+
if (kd == null) {
70+
kd = ctxt.findKeyDeserializer(type.getKeyType(), property);
71+
}
72+
JsonDeserializer<?> valueDeser = elementDeserializer;
73+
final JavaType vt = type.getContentType();
74+
if (valueDeser == null) {
75+
valueDeser = ctxt.findContextualValueDeserializer(vt, property);
76+
} else { // if directly assigned, probably not yet contextual, so:
77+
valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt);
78+
}
79+
// Type deserializer is slightly different; must be passed, but needs to become contextual:
80+
TypeDeserializer vtd = elementTypeDeserializer;
81+
if (vtd != null) {
82+
vtd = vtd.forProperty(property);
83+
}
84+
return _createContextual(type, kd, vtd, valueDeser,
85+
findContentNullProvider(ctxt, property, valueDeser));
86+
}
87+
88+
/*
89+
/**********************************************************************
90+
/* Abstract methods for subclasses
91+
/**********************************************************************
92+
*/
93+
94+
protected abstract T createCache();
95+
96+
protected abstract JsonDeserializer<?> _createContextual(MapLikeType t, KeyDeserializer kd,
97+
TypeDeserializer vtd, JsonDeserializer<?> vd, NullValueProvider np);
98+
99+
/*
100+
/**********************************************************************
101+
/* Implementations
102+
/**********************************************************************
103+
*/
104+
105+
@Override
106+
public LogicalType logicalType() {
107+
return LogicalType.Map;
108+
}
109+
110+
@Override
111+
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
112+
return deserializeContents(p, ctxt);
113+
}
114+
115+
private T deserializeContents(JsonParser p, DeserializationContext ctxt)
116+
throws IOException
117+
{
118+
T cache = createCache();
119+
120+
JsonToken currToken = p.currentToken();
121+
if (currToken != JsonToken.FIELD_NAME) {
122+
// 01-Mar-2023, tatu: [datatypes-collections#104] Handle empty Maps too
123+
if (currToken != JsonToken.END_OBJECT) {
124+
expect(p, JsonToken.START_OBJECT);
125+
currToken = p.nextToken();
126+
}
127+
}
128+
129+
for (; currToken == JsonToken.FIELD_NAME; currToken = p.nextToken()) {
130+
final Object key;
131+
if (keyDeserializer != null) {
132+
key = keyDeserializer.deserializeKey(p.currentName(), ctxt);
133+
} else {
134+
key = p.currentName();
135+
}
136+
137+
p.nextToken();
138+
139+
final Object value;
140+
if (p.currentToken() == JsonToken.VALUE_NULL) {
141+
if (skipNullValues) {
142+
continue;
143+
}
144+
value = nullProvider.getNullValue(ctxt);
145+
} else if (elementTypeDeserializer != null) {
146+
value = elementDeserializer.deserializeWithType(p, ctxt, elementTypeDeserializer);
147+
} else {
148+
value = elementDeserializer.deserialize(p, ctxt);
149+
}
150+
cache.put(key, value);
151+
}
152+
return cache;
153+
}
154+
155+
private void expect(JsonParser p, JsonToken token) throws IOException {
156+
if (p.getCurrentToken() != token) {
157+
throw new JsonMappingException(p, "Expecting " + token + " to start `Cache` value, found " + p.currentToken(),
158+
p.getCurrentLocation());
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)