Skip to content

Commit d8442de

Browse files
adds support for deserialization of hash-based table
1 parent 3dff78f commit d8442de

5 files changed

Lines changed: 246 additions & 33 deletions

File tree

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

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

3+
import com.fasterxml.jackson.datatype.guava.deser.table.HashTableDeserializer;
34
import java.io.Serializable;
45

56
import com.google.common.base.Optional;
@@ -258,11 +259,8 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
258259
elementTypeDeserializer, elementDeserializer);
259260
}
260261

261-
if (Table.class.isAssignableFrom(raw)) {
262-
// !!! TODO
263-
}
264262
// @since 2.16 : support Cache deserialization
265-
java.util.Optional<JsonDeserializer<?>> cacheDeserializer = findCacheDeserializer(raw, type, config,
263+
java.util.Optional<JsonDeserializer<?>> cacheDeserializer = findCacheDeserializer(raw, type, config,
266264
beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer);
267265
if (cacheDeserializer.isPresent()) {
268266
return cacheDeserializer.get();
@@ -281,9 +279,9 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
281279
* @return An optional {@link JsonDeserializer} for the cache type, if found.
282280
* @since 2.16
283281
*/
284-
private java.util.Optional<JsonDeserializer<?>> findCacheDeserializer(Class<?> raw, MapLikeType type,
285-
DeserializationConfig config, BeanDescription beanDesc, KeyDeserializer keyDeserializer,
286-
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
282+
private java.util.Optional<JsonDeserializer<?>> findCacheDeserializer(Class<?> raw, MapLikeType type,
283+
DeserializationConfig config, BeanDescription beanDesc, KeyDeserializer keyDeserializer,
284+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
287285
{
288286
/* // Example implementations
289287
if (LoadingCache.class.isAssignableFrom(raw)) {
@@ -315,9 +313,15 @@ public JsonDeserializer<?> findReferenceDeserializer(ReferenceType refType,
315313
public JsonDeserializer<?> findBeanDeserializer(final JavaType type, DeserializationConfig config,
316314
BeanDescription beanDesc)
317315
{
318-
if (RangeSet.class.isAssignableFrom(type.getRawClass())) {
316+
Class<?> raw = type.getRawClass();
317+
if (RangeSet.class.isAssignableFrom(raw)) {
319318
return new RangeSetDeserializer();
320319
}
320+
if (Table.class.isAssignableFrom(raw)) {
321+
if (HashBasedTable.class.isAssignableFrom(raw)) {
322+
return new HashTableDeserializer(type);
323+
}
324+
}
321325
if (type.hasRawClass(Range.class)) {
322326
return new RangeDeserializer(_defaultBoundType, type);
323327
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.fasterxml.jackson.datatype.guava.deser.table;
2+
3+
import com.fasterxml.jackson.core.JsonParser;
4+
import com.fasterxml.jackson.core.JsonToken;
5+
import com.fasterxml.jackson.databind.BeanProperty;
6+
import com.fasterxml.jackson.databind.DeserializationContext;
7+
import com.fasterxml.jackson.databind.JavaType;
8+
import com.fasterxml.jackson.databind.JsonDeserializer;
9+
import com.fasterxml.jackson.databind.JsonMappingException;
10+
import com.fasterxml.jackson.databind.KeyDeserializer;
11+
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
12+
import com.fasterxml.jackson.databind.deser.NullValueProvider;
13+
import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
14+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
15+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
16+
import com.google.common.collect.Table;
17+
import java.io.IOException;
18+
19+
/**
20+
* @author Abhishekkr3003
21+
*/
22+
public abstract class GuavaTableDeserializer<T extends Table<Object, Object, Object>>
23+
extends StdDeserializer<T> implements ContextualDeserializer {
24+
private static final long serialVersionUID = 1L;
25+
26+
private final JavaType _type;
27+
private final KeyDeserializer _rowDeserializer;
28+
private final KeyDeserializer _colDeserializer;
29+
private final TypeDeserializer _valueTypeDeserializer;
30+
private final JsonDeserializer<?> _valueDeserializer;
31+
32+
// since 2.9.5: in 3.x demote to `ContainerDeserializerBase`
33+
private final NullValueProvider _nullProvider;
34+
private final boolean _skipNullValues;
35+
36+
public GuavaTableDeserializer(JavaType _type, KeyDeserializer _rowDeserializer,
37+
KeyDeserializer _colDeserializer, TypeDeserializer _valueTypeDeserializer,
38+
JsonDeserializer<?> _valueDeserializer) {
39+
this(
40+
_type, _rowDeserializer, _colDeserializer, _valueTypeDeserializer, _valueDeserializer,
41+
null
42+
);
43+
}
44+
45+
public GuavaTableDeserializer(JavaType _type, KeyDeserializer _rowDeserializer,
46+
KeyDeserializer _colDeserializer, TypeDeserializer _valueTypeDeserializer,
47+
JsonDeserializer<?> _valueDeserializer, NullValueProvider nvp) {
48+
super(_type);
49+
this._type = _type;
50+
this._rowDeserializer = _rowDeserializer;
51+
this._colDeserializer = _colDeserializer;
52+
this._valueTypeDeserializer = _valueTypeDeserializer;
53+
this._valueDeserializer = _valueDeserializer;
54+
this._nullProvider = nvp;
55+
_skipNullValues = (nvp == null) ? false : NullsConstantProvider.isSkipper(nvp);
56+
}
57+
58+
public GuavaTableDeserializer(JavaType type) {
59+
super(type);
60+
_type = type;
61+
_rowDeserializer = null;
62+
_colDeserializer = null;
63+
_valueDeserializer = null;
64+
_valueTypeDeserializer = null;
65+
_nullProvider = null;
66+
_skipNullValues = false;
67+
}
68+
69+
protected abstract T createTable();
70+
71+
/**
72+
* We need to use this method to properly handle possible contextual variants of key and value
73+
* deserializers, as well as type deserializers.
74+
*/
75+
@Override
76+
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
77+
BeanProperty property) throws JsonMappingException {
78+
KeyDeserializer rkd = _rowDeserializer;
79+
if (rkd == null) {
80+
rkd = ctxt.findKeyDeserializer(_type.containedTypeOrUnknown(0), property);
81+
}
82+
KeyDeserializer ckd = _colDeserializer;
83+
if (ckd == null) {
84+
ckd = ctxt.findKeyDeserializer(_type.containedTypeOrUnknown(1), property);
85+
}
86+
JsonDeserializer<?> valueDeser = _valueDeserializer;
87+
final JavaType vt = _type.containedTypeOrUnknown(2);
88+
if (valueDeser == null) {
89+
valueDeser = ctxt.findContextualValueDeserializer(vt, property);
90+
} else { // if directly assigned, probably not yet contextual, so:
91+
valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt);
92+
}
93+
// Type deserializer is slightly different; must be passed, but needs to become contextual:
94+
TypeDeserializer vtd = _valueTypeDeserializer;
95+
if (vtd != null) {
96+
vtd = vtd.forProperty(property);
97+
}
98+
return _createContextual(
99+
_type, rkd, ckd, vtd, valueDeser, findContentNullProvider(ctxt, property, valueDeser));
100+
}
101+
102+
protected abstract JsonDeserializer<?> _createContextual(JavaType t, KeyDeserializer rkd,
103+
KeyDeserializer ckd, TypeDeserializer vtd, JsonDeserializer<?> vd, NullValueProvider np);
104+
105+
@Override
106+
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
107+
T table = createTable();
108+
109+
JsonToken currToken = p.currentToken();
110+
if (currToken != JsonToken.FIELD_NAME && currToken != JsonToken.END_OBJECT) {
111+
expect(p, JsonToken.START_OBJECT);
112+
currToken = p.nextToken();
113+
}
114+
115+
for (; currToken == JsonToken.FIELD_NAME; currToken = p.nextToken()) {
116+
final Object rowKey;
117+
if (_rowDeserializer != null) {
118+
rowKey = _rowDeserializer.deserializeKey(p.currentName(), ctxt);
119+
} else {
120+
rowKey = p.currentName();
121+
}
122+
123+
p.nextToken();
124+
expect(p, JsonToken.START_OBJECT);
125+
126+
for (
127+
currToken = p.nextToken(); currToken == JsonToken.FIELD_NAME;
128+
currToken = p.nextToken()) {
129+
final Object colKey;
130+
if (_colDeserializer != null) {
131+
colKey = _colDeserializer.deserializeKey(p.currentName(), ctxt);
132+
} else {
133+
colKey = p.currentName();
134+
}
135+
136+
p.nextToken();
137+
138+
final Object value;
139+
if (p.currentToken() == JsonToken.VALUE_NULL) {
140+
if (_skipNullValues) {
141+
continue;
142+
}
143+
value = _nullProvider.getNullValue(ctxt);
144+
} else if (_valueTypeDeserializer != null) {
145+
value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
146+
} else {
147+
value = _valueDeserializer.deserialize(p, ctxt);
148+
}
149+
table.put(rowKey, colKey, value);
150+
}
151+
expect(p, JsonToken.END_OBJECT);
152+
}
153+
return table;
154+
}
155+
156+
private void expect(JsonParser p, JsonToken token) throws IOException {
157+
if (p.getCurrentToken() != token) {
158+
throw new JsonMappingException(
159+
p,
160+
"Expecting " + token + " to start `TABLE` value, found " + p.currentToken(),
161+
p.currentLocation()
162+
);
163+
}
164+
}
165+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.fasterxml.jackson.datatype.guava.deser.table;
2+
3+
import com.fasterxml.jackson.databind.JavaType;
4+
import com.fasterxml.jackson.databind.JsonDeserializer;
5+
import com.fasterxml.jackson.databind.KeyDeserializer;
6+
import com.fasterxml.jackson.databind.deser.NullValueProvider;
7+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
8+
import com.google.common.collect.HashBasedTable;
9+
10+
/**
11+
* Provides deserialization for the Guava HashBasedTable class.
12+
*
13+
* @author Abhishekkr3003
14+
*/
15+
public class HashTableDeserializer
16+
extends GuavaTableDeserializer<HashBasedTable<Object, Object, Object>> {
17+
private static final long serialVersionUID = 1L;
18+
19+
public HashTableDeserializer(JavaType type) {
20+
super(type);
21+
}
22+
23+
public HashTableDeserializer(JavaType type, KeyDeserializer rowDeserializer,
24+
KeyDeserializer columnDeserializer, TypeDeserializer elementTypeDeserializer,
25+
JsonDeserializer<?> elementDeserializer) {
26+
super(type, rowDeserializer, columnDeserializer, elementTypeDeserializer,
27+
elementDeserializer
28+
);
29+
}
30+
31+
public HashTableDeserializer(JavaType type, KeyDeserializer rowDeserializer,
32+
KeyDeserializer columnDeserializer, TypeDeserializer elementTypeDeserializer,
33+
JsonDeserializer<?> elementDeserializer, NullValueProvider nvp) {
34+
super(type, rowDeserializer, columnDeserializer, elementTypeDeserializer,
35+
elementDeserializer, nvp
36+
);
37+
}
38+
39+
@Override
40+
protected HashBasedTable<Object, Object, Object> createTable() {
41+
return HashBasedTable.create();
42+
}
43+
44+
@Override
45+
protected JsonDeserializer<?> _createContextual(JavaType type, KeyDeserializer rowDeserializer,
46+
KeyDeserializer columnDeserializer, TypeDeserializer typeDeserializer,
47+
JsonDeserializer<?> elementDeserializer, NullValueProvider nvp) {
48+
return new HashTableDeserializer(
49+
type, rowDeserializer, columnDeserializer, typeDeserializer, elementDeserializer, nvp);
50+
}
51+
}

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

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

3+
import com.google.common.collect.HashBasedTable;
34
import java.io.IOException;
45

56
import com.fasterxml.jackson.core.JsonGenerator;
@@ -8,7 +9,6 @@
89
import com.fasterxml.jackson.databind.*;
910
import com.fasterxml.jackson.databind.module.SimpleModule;
1011

11-
import com.google.common.collect.ImmutableTable;
1212

1313
public class TableSerializationTest extends ModuleTestBase
1414
{
@@ -121,44 +121,37 @@ else if ( !this.key2.equals(other.key2)) {
121121

122122
public void testSimpleKeyImmutableTableSerde() throws IOException
123123
{
124-
final ImmutableTable.Builder<Integer, String, String> builder = ImmutableTable.builder();
125-
builder.put(Integer.valueOf(42), "column42", "some value 42");
126-
builder.put(Integer.valueOf(45), "column45", "some value 45");
127-
final ImmutableTable<Integer, String, String> simpleTable = builder.build();
124+
final HashBasedTable<Integer, String, String> simpleTable = HashBasedTable.create();
125+
simpleTable.put(Integer.valueOf(42), "column42", "some value 42");
126+
simpleTable.put(Integer.valueOf(45), "column45", "some value 45");
128127

129128
final String simpleJson = MAPPER.writeValueAsString(simpleTable);
130129
assertEquals("{\"42\":{\"column42\":\"some value 42\"},\"45\":{\"column45\":\"some value 45\"}}", simpleJson);
131-
132-
// !!! TODO: support deser
133130

134-
/*
135-
final ImmutableTable<Integer, String, String> reconstitutedTable =
136-
this.MAPPER.readValue(simpleJson, new TypeReference<ImmutableTable<Integer, String, String>>() {});
131+
final HashBasedTable<Integer, String, String> reconstitutedTable =
132+
this.MAPPER.readValue(simpleJson,
133+
new TypeReference<HashBasedTable<Integer, String, String>>() {
134+
}
135+
);
137136
assertEquals(simpleTable, reconstitutedTable);
138-
*/
139137
}
140138

141139
/**
142140
* This test illustrates one way to use objects as keys in Tables.
143141
*/
144142
public void testComplexKeyImmutableTableSerde() throws IOException
145143
{
146-
final ImmutableTable.Builder<Integer, ComplexKey, String> ckBuilder = ImmutableTable.builder();
147-
ckBuilder.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42");
148-
ckBuilder.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45");
149-
final ImmutableTable<Integer, ComplexKey, String> complexKeyTable = ckBuilder.build();
150-
151-
final TypeReference<ImmutableTable<Integer, ComplexKey, String>> tableType = new TypeReference<ImmutableTable<Integer, ComplexKey, String>>()
144+
final HashBasedTable<Integer, ComplexKey, String> complexKeyTable = HashBasedTable.create();
145+
complexKeyTable.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42");
146+
complexKeyTable.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45");
147+
148+
final TypeReference<HashBasedTable<Integer, ComplexKey, String>> tableType = new TypeReference<HashBasedTable<Integer, ComplexKey, String>>()
152149
{};
153-
150+
154151
final String ckJson = this.MAPPER.writerFor(tableType).writeValueAsString(complexKeyTable);
155152
assertEquals("{\"42\":{\"field1:field2\":\"some value 42\"},\"45\":{\"field1:field2\":\"some value 45\"}}", ckJson);
156-
157-
// !!! TODO: support deser
158-
/*
159153

160-
final ImmutableTable<Integer, ComplexKey, String> reconstitutedTable = this.MAPPER.readValue(ckJson, tableType);
154+
final HashBasedTable<Integer, ComplexKey, String> reconstitutedTable = this.MAPPER.readValue(ckJson, tableType);
161155
assertEquals(complexKeyTable, reconstitutedTable);
162-
*/
163156
}
164157
}

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2-
<modelVersion>4.0.0</modelVersion>
2+
<modelVersion>4.0.0</modelVersion>
33
<parent>
44
<groupId>com.fasterxml.jackson</groupId>
55
<artifactId>jackson-base</artifactId>
@@ -105,5 +105,5 @@
105105
</plugin>
106106
</plugins>
107107
</build>
108-
108+
109109
</project>

0 commit comments

Comments
 (0)