Skip to content

Commit 6063276

Browse files
authored
Serializer and deserializer of Map and Multimap should be contextual (#158)
* Reproduce problem * Map serialization is contextual * Avoid class casting: JavaType -> MapLikeType * Support custom key-serializer in MaplikeDeserializer * Support custom value-serializer in MaplikeDeserializer * Move KeySerializer lookup to contextualization * Move value-serializer lookup to contextualization * Handle secondary contextualization in MaplikeDeserializer * Support key-contextualization in MultiMap * Test value-contextualization * Test secondary contextualization of key deserialization * Fix tests by introducing TypeDeserializer for element * Fix test
1 parent 0b6eec8 commit 6063276

File tree

8 files changed

+454
-40
lines changed

8 files changed

+454
-40
lines changed

src/main/java/io/vavr/jackson/datatype/deserialize/MapDeserializer.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import com.fasterxml.jackson.core.JsonParser;
2323
import com.fasterxml.jackson.core.JsonToken;
2424
import com.fasterxml.jackson.databind.DeserializationContext;
25-
import com.fasterxml.jackson.databind.JavaType;
25+
import com.fasterxml.jackson.databind.JsonDeserializer;
26+
import com.fasterxml.jackson.databind.KeyDeserializer;
27+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
28+
import com.fasterxml.jackson.databind.type.MapLikeType;
2629
import io.vavr.Tuple;
2730
import io.vavr.Tuple2;
2831
import io.vavr.collection.*;
@@ -33,8 +36,20 @@ class MapDeserializer extends MaplikeDeserializer<Map<?, ?>> {
3336

3437
private static final long serialVersionUID = 1L;
3538

36-
MapDeserializer(JavaType valueType) {
37-
super(valueType);
39+
MapDeserializer(MapLikeType mapType, KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer,
40+
JsonDeserializer<?> elementDeserializer) {
41+
super(mapType, null, keyDeserializer, elementTypeDeserializer, elementDeserializer);
42+
}
43+
44+
MapDeserializer(MapDeserializer origin, KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> valueDeserializer) {
45+
super(origin.mapType, origin.keyComparator, keyDeserializer, elementTypeDeserializer, valueDeserializer);
46+
}
47+
48+
@Override
49+
MaplikeDeserializer<Map<?, ?>> createDeserializer(KeyDeserializer keyDeserializer,
50+
TypeDeserializer elementTypeDeserializer,
51+
JsonDeserializer<?> valueDeserializer) {
52+
return new MapDeserializer(this, keyDeserializer, elementTypeDeserializer, valueDeserializer);
3853
}
3954

4055
@Override
@@ -47,9 +62,11 @@ class MapDeserializer extends MaplikeDeserializer<Map<?, ?>> {
4762
// Note: must handle null explicitly here; value deserializers won't
4863
Object value;
4964
if (t == JsonToken.VALUE_NULL) {
50-
value = valueDeserializer.getNullValue(ctxt);
65+
value = elementDeserializer.getNullValue(ctxt);
66+
} else if (elementTypeDeserializer == null) {
67+
value = elementDeserializer.deserialize(p, ctxt);
5168
} else {
52-
value = valueDeserializer.deserialize(p, ctxt);
69+
value = elementDeserializer.deserializeWithType(p, ctxt, elementTypeDeserializer);
5370
}
5471
result.add(Tuple.of(key, value));
5572
}

src/main/java/io/vavr/jackson/datatype/deserialize/MaplikeDeserializer.java

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,79 @@
2020
package io.vavr.jackson.datatype.deserialize;
2121

2222
import com.fasterxml.jackson.databind.*;
23+
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
24+
import com.fasterxml.jackson.databind.deser.ContextualKeyDeserializer;
2325
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
2426
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
27+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
2528
import com.fasterxml.jackson.databind.type.MapLikeType;
2629

2730
import java.io.Serializable;
2831
import java.util.Comparator;
2932

30-
abstract class MaplikeDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer {
33+
abstract class MaplikeDeserializer<T> extends StdDeserializer<T> implements ResolvableDeserializer, ContextualDeserializer {
3134

3235
private static final long serialVersionUID = 1L;
3336

34-
final MapLikeType javaType;
37+
final MapLikeType mapType;
3538

3639
Comparator<Object> keyComparator;
37-
KeyDeserializer keyDeserializer;
38-
JsonDeserializer<?> valueDeserializer;
40+
final KeyDeserializer keyDeserializer;
41+
final TypeDeserializer elementTypeDeserializer;
42+
final JsonDeserializer<?> elementDeserializer;
3943

40-
MaplikeDeserializer(JavaType valueType) {
41-
super(valueType);
42-
this.javaType = (MapLikeType) valueType;
44+
MaplikeDeserializer(MapLikeType mapType, Comparator<Object> keyComparator, KeyDeserializer keyDeserializer,
45+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
46+
super(mapType);
47+
this.mapType = mapType;
48+
this.keyComparator = keyComparator;
49+
this.keyDeserializer = keyDeserializer;
50+
this.elementTypeDeserializer = elementTypeDeserializer;
51+
this.elementDeserializer = elementDeserializer;
4352
}
4453

54+
/**
55+
* Creates a new deserializer from the original one (this).
56+
*
57+
* @param keyDeserializer the new deserializer for key
58+
* @param elementTypeDeserializer the new deserializer for element type
59+
* @param elementDeserializer the new deserializer for element
60+
* @return a new deserializer
61+
*/
62+
abstract MaplikeDeserializer<T> createDeserializer(KeyDeserializer keyDeserializer,
63+
TypeDeserializer elementTypeDeserializer,
64+
JsonDeserializer<?> elementDeserializer);
65+
4566
@SuppressWarnings("unchecked")
4667
@Override
4768
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
48-
JavaType keyType = javaType.getKeyType();
69+
JavaType keyType = mapType.getKeyType();
4970
if (Comparable.class.isAssignableFrom(keyType.getRawClass())) {
5071
keyComparator = (Comparator<Object> & Serializable) (o1, o2) -> ((Comparable<Object>) o1).compareTo(o2);
5172
} else {
5273
keyComparator = (Comparator<Object> & Serializable) (o1, o2) -> o1.toString().compareTo(o2.toString());
5374
}
54-
keyDeserializer = ctxt.findKeyDeserializer(keyType, null);
55-
valueDeserializer = ctxt.findRootValueDeserializer(javaType.getContentType());
75+
}
76+
77+
@Override
78+
public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
79+
KeyDeserializer keyDeser = keyDeserializer;
80+
if (keyDeser == null) {
81+
keyDeser = context.findKeyDeserializer(mapType.getKeyType(), property);
82+
} else if (keyDeser instanceof ContextualKeyDeserializer) {
83+
keyDeser = ((ContextualKeyDeserializer) keyDeser).createContextual(context, property);
84+
}
85+
86+
TypeDeserializer elementTypeDeser = elementTypeDeserializer;
87+
if (elementTypeDeser != null) {
88+
elementTypeDeser = elementTypeDeser.forProperty(property);
89+
}
90+
JsonDeserializer<?> elementDeser = elementDeserializer;
91+
if (elementDeser == null) {
92+
elementDeser = context.findContextualValueDeserializer(mapType.getContentType(), property);
93+
} else {
94+
elementDeser = context.handleSecondaryContextualization(elementDeser, property, mapType.getContentType());
95+
}
96+
return createDeserializer(keyDeser, elementTypeDeser, elementDeser);
5697
}
5798
}

src/main/java/io/vavr/jackson/datatype/deserialize/MultimapDeserializer.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121

2222
import com.fasterxml.jackson.core.JsonParser;
2323
import com.fasterxml.jackson.core.JsonToken;
24-
import com.fasterxml.jackson.databind.DeserializationContext;
25-
import com.fasterxml.jackson.databind.JavaType;
26-
import com.fasterxml.jackson.databind.JsonDeserializer;
27-
import com.fasterxml.jackson.databind.JsonMappingException;
24+
import com.fasterxml.jackson.databind.*;
25+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
26+
import com.fasterxml.jackson.databind.type.MapLikeType;
2827
import io.vavr.Tuple;
2928
import io.vavr.Tuple2;
3029
import io.vavr.collection.HashMultimap;
@@ -41,14 +40,27 @@ class MultimapDeserializer extends MaplikeDeserializer<Multimap<?, ?>> {
4140

4241
private JsonDeserializer<?> containerDeserializer;
4342

44-
MultimapDeserializer(JavaType valueType) {
45-
super(valueType);
43+
MultimapDeserializer(MapLikeType mapType, KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer,
44+
JsonDeserializer<?> elementDeserializer) {
45+
super(mapType, null, keyDeserializer, elementTypeDeserializer, elementDeserializer);
46+
}
47+
48+
MultimapDeserializer(MultimapDeserializer origin, KeyDeserializer keyDeserializer,
49+
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
50+
super(origin.mapType, origin.keyComparator, keyDeserializer, elementTypeDeserializer, elementDeserializer);
51+
containerDeserializer = origin.containerDeserializer;
52+
}
53+
54+
@Override
55+
MultimapDeserializer createDeserializer(KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer,
56+
JsonDeserializer<?> elementDeserializer) {
57+
return new MultimapDeserializer(this, keyDeserializer, elementTypeDeserializer, elementDeserializer);
4658
}
4759

4860
@Override
4961
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
5062
super.resolve(ctxt);
51-
JavaType containerType = ctxt.getTypeFactory().constructCollectionType(ArrayList.class, javaType.getContentType());
63+
JavaType containerType = ctxt.getTypeFactory().constructCollectionType(ArrayList.class, mapType.getContentType());
5264
containerDeserializer = ctxt.findContextualValueDeserializer(containerType, null);
5365
}
5466

src/main/java/io/vavr/jackson/datatype/deserialize/VavrDeserializers.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,10 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
185185
{
186186
Class<?> raw = type.getRawClass();
187187
if (Map.class.isAssignableFrom(raw)) {
188-
return new MapDeserializer(type);
188+
return new MapDeserializer(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
189189
}
190190
if (Multimap.class.isAssignableFrom(raw)) {
191-
return new MultimapDeserializer(type);
191+
return new MultimapDeserializer(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
192192
}
193193
return super.findMapLikeDeserializer(type, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer);
194194
}

src/main/java/io/vavr/jackson/datatype/serialize/MapSerializer.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,25 @@
1919
*/
2020
package io.vavr.jackson.datatype.serialize;
2121

22-
import com.fasterxml.jackson.databind.JavaType;
23-
import com.fasterxml.jackson.databind.SerializerProvider;
22+
import com.fasterxml.jackson.databind.*;
23+
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
2424
import com.fasterxml.jackson.databind.type.MapLikeType;
2525
import com.fasterxml.jackson.databind.type.TypeFactory;
2626
import io.vavr.collection.Map;
2727

2828
import java.io.IOException;
2929
import java.util.LinkedHashMap;
3030

31-
class MapSerializer extends ValueSerializer<Map<?, ?>> {
31+
class MapSerializer extends ValueSerializer<Map<?, ?>> implements ContextualSerializer {
3232

3333
private static final long serialVersionUID = 1L;
3434

35+
MapSerializer(JavaType type, BeanProperty beanProperty) {
36+
super(type, beanProperty);
37+
}
38+
3539
MapSerializer(JavaType type) {
36-
super(type);
40+
this(type, null);
3741
}
3842

3943
@Override
@@ -51,4 +55,12 @@ JavaType emulatedJavaType(JavaType type, TypeFactory typeFactory) {
5155
public boolean isEmpty(SerializerProvider provider, Map<?, ?> value) {
5256
return value.isEmpty();
5357
}
58+
59+
@Override
60+
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
61+
if (property == beanProperty) {
62+
return this;
63+
}
64+
return new MapSerializer(type, property);
65+
}
5466
}

src/main/java/io/vavr/jackson/datatype/serialize/MultimapSerializer.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
*/
2020
package io.vavr.jackson.datatype.serialize;
2121

22-
import com.fasterxml.jackson.databind.JavaType;
23-
import com.fasterxml.jackson.databind.SerializerProvider;
22+
import com.fasterxml.jackson.databind.*;
23+
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
24+
import com.fasterxml.jackson.databind.type.MapLikeType;
2425
import com.fasterxml.jackson.databind.type.TypeFactory;
2526
import io.vavr.collection.Multimap;
2627

@@ -29,12 +30,16 @@
2930
import java.util.LinkedHashMap;
3031
import java.util.List;
3132

32-
class MultimapSerializer extends ValueSerializer<Multimap<?, ?>> {
33+
class MultimapSerializer extends ValueSerializer<Multimap<?, ?>> implements ContextualSerializer {
3334

3435
private static final long serialVersionUID = 1L;
3536

3637
MultimapSerializer(JavaType type) {
37-
super(type);
38+
this(type, null);
39+
}
40+
41+
MultimapSerializer(JavaType type, BeanProperty beanProperty) {
42+
super(type, beanProperty);
3843
}
3944

4045
@Override
@@ -53,12 +58,21 @@ Object toJavaObj(Multimap<?, ?> value) throws IOException {
5358

5459
@Override
5560
JavaType emulatedJavaType(JavaType type, TypeFactory typeFactory) {
56-
JavaType containerType = typeFactory.constructCollectionType(ArrayList.class, type.containedType(1));
57-
return typeFactory.constructMapType(LinkedHashMap.class, type.containedType(0), containerType);
61+
MapLikeType mapType = (MapLikeType) type;
62+
JavaType containerType = typeFactory.constructCollectionType(ArrayList.class, mapType.getContentType());
63+
return typeFactory.constructMapType(LinkedHashMap.class, mapType.getKeyType(), containerType);
5864
}
5965

6066
@Override
6167
public boolean isEmpty(SerializerProvider provider, Multimap<?, ?> value) {
6268
return value.isEmpty();
6369
}
70+
71+
@Override
72+
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
73+
if (property == beanProperty) {
74+
return this;
75+
}
76+
return new MultimapSerializer(type, property);
77+
}
6478
}

0 commit comments

Comments
 (0)