Skip to content

Commit ead4e32

Browse files
committed
Fix #47, one of oldest open bugs (yay!): support @JsonValue for Map keys
1 parent 63b33fb commit ead4e32

File tree

7 files changed

+64
-40
lines changed

7 files changed

+64
-40
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Project: jackson-databind
66

77
2.5.0 (not yet released)
88

9+
#47: Support `@JsonValue` for (Map) key serialization
910
#113: Problem deserializing polymorphic types with @JsonCreator
1011
#408: External type id does not allow use of 'visible=true'
1112
#421: @JsonCreator not used in case of multiple creators with parameter names

src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ public final boolean isEnabled(JsonParser.Feature f, JsonFactory factory) {
653653
}
654654
return factory.isEnabled(f);
655655
}
656-
656+
657657
/**
658658
* "Bulk" access method for checking that all features specified by
659659
* mask are enabled.
@@ -673,7 +673,7 @@ public final int getDeserializationFeatures() {
673673
/* Other configuration
674674
/**********************************************************
675675
*/
676-
676+
677677
/**
678678
* Method for getting head of the problem handler chain. May be null,
679679
* if no handlers have been added.

src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import com.fasterxml.jackson.annotation.JsonFormat;
1212
import com.fasterxml.jackson.annotation.JsonInclude;
13-
1413
import com.fasterxml.jackson.databind.*;
1514
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
1615
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
@@ -206,6 +205,7 @@ public JsonSerializer<Object> createKeySerializer(SerializationConfig config,
206205
JavaType keyType, JsonSerializer<Object> defaultImpl)
207206
{
208207
// We should not need any member method info; at most class annotations for Map type
208+
// ... at least, not here.
209209
BeanDescription beanDesc = config.introspectClassAnnotations(keyType.getRawClass());
210210
JsonSerializer<?> ser = null;
211211
// Minor optimization: to avoid constructing beanDesc, bail out if none registered
@@ -221,7 +221,20 @@ public JsonSerializer<Object> createKeySerializer(SerializationConfig config,
221221
if (ser == null) {
222222
ser = defaultImpl;
223223
if (ser == null) {
224-
ser = StdKeySerializers.getStdKeySerializer(keyType);
224+
ser = StdKeySerializers.getStdKeySerializer(config, keyType.getRawClass(), false);
225+
// As per [databind#47], also need to support @JsonValue
226+
if (ser == null) {
227+
beanDesc = config.introspect(keyType);
228+
AnnotatedMethod am = beanDesc.findJsonValueMethod();
229+
if (am != null) {
230+
final Class<?> rawType = am.getRawReturnType();
231+
JsonSerializer<?> delegate = StdKeySerializers.getStdKeySerializer(config,
232+
rawType, true);
233+
ser = new JsonValueSerializer(am.getAnnotated(), delegate);
234+
} else {
235+
ser = StdKeySerializers.getDefault();
236+
}
237+
}
225238
}
226239
}
227240

src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.lang.reflect.Type;
88

99
import com.fasterxml.jackson.core.*;
10-
1110
import com.fasterxml.jackson.databind.*;
1211
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
1312
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
@@ -60,11 +59,12 @@ public class JsonValueSerializer
6059
* {@link com.fasterxml.jackson.databind.annotation.JsonSerialize#using}), otherwise
6160
* null
6261
*/
63-
public JsonValueSerializer(Method valueMethod, JsonSerializer<Object> ser)
62+
@SuppressWarnings("unchecked")
63+
public JsonValueSerializer(Method valueMethod, JsonSerializer<?> ser)
6464
{
65-
super(Object.class);
65+
super(valueMethod.getReturnType(), false);
6666
_accessorMethod = valueMethod;
67-
_valueSerializer = ser;
67+
_valueSerializer = (JsonSerializer<Object>) ser;
6868
_property = null;
6969
_forceTypeInformation = true; // gets reconsidered when we are contextualized
7070
}
@@ -294,8 +294,7 @@ protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, JsonSerializer<
294294
*/
295295

296296
@Override
297-
public String toString()
298-
{
297+
public String toString() {
299298
return "(@JsonValue serializer for method " + _accessorMethod.getDeclaringClass() + "#" + _accessorMethod.getName() + ")";
300299
}
301300
}

src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializer.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
public class StdKeySerializer extends StdSerializer<Object>
2121
{
2222
public StdKeySerializer() { super(Object.class); }
23-
23+
2424
@Override
25-
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
25+
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
2626
if (value instanceof Date) {
2727
provider.defaultSerializeDateKey((Date) value, jgen);
2828
} else {
@@ -34,7 +34,7 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi
3434
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
3535
return createSchemaNode("string");
3636
}
37-
37+
3838
@Override
3939
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
4040
visitor.expectStringFormat(typeHint);

src/main/java/com/fasterxml/jackson/databind/ser/std/StdKeySerializers.java

+37-26
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
import java.util.Date;
66

77
import com.fasterxml.jackson.core.*;
8-
9-
import com.fasterxml.jackson.databind.JavaType;
10-
import com.fasterxml.jackson.databind.JsonSerializer;
11-
import com.fasterxml.jackson.databind.SerializerProvider;
8+
import com.fasterxml.jackson.databind.*;
129

1310
public class StdKeySerializers
1411
{
@@ -20,33 +17,47 @@ public class StdKeySerializers
2017

2118
private StdKeySerializers() { }
2219

20+
/**
21+
* @param config Serialization configuration in use, may be needed in choosing
22+
* serializer to use
23+
* @param rawKeyType Type of key values to serialize
24+
* @param useDefault If no match is found, should we return fallback deserializer
25+
* (true), or null (false)?
26+
*/
2327
@SuppressWarnings("unchecked")
24-
public static JsonSerializer<Object> getStdKeySerializer(JavaType keyType)
28+
public static JsonSerializer<Object> getStdKeySerializer(SerializationConfig config,
29+
Class<?> rawKeyType, boolean useDefault)
2530
{
26-
if (keyType == null) {
27-
return DEFAULT_KEY_SERIALIZER;
28-
}
29-
Class<?> cls = keyType.getRawClass();
30-
if (cls == String.class) {
31-
return DEFAULT_STRING_SERIALIZER;
32-
}
33-
if (cls == Object.class || cls.isPrimitive() || Number.class.isAssignableFrom(cls)) {
34-
return DEFAULT_KEY_SERIALIZER;
35-
}
36-
if (Date.class.isAssignableFrom(cls)) {
37-
return (JsonSerializer<Object>) DateKeySerializer.instance;
38-
}
39-
if (Calendar.class.isAssignableFrom(cls)) {
40-
return (JsonSerializer<Object>) CalendarKeySerializer.instance;
31+
if (rawKeyType != null) {
32+
if (rawKeyType == String.class) {
33+
return DEFAULT_STRING_SERIALIZER;
34+
}
35+
if (rawKeyType == Object.class || rawKeyType.isPrimitive()
36+
|| Number.class.isAssignableFrom(rawKeyType)) {
37+
return DEFAULT_KEY_SERIALIZER;
38+
}
39+
if (Date.class.isAssignableFrom(rawKeyType)) {
40+
return (JsonSerializer<Object>) DateKeySerializer.instance;
41+
}
42+
if (Calendar.class.isAssignableFrom(rawKeyType)) {
43+
return (JsonSerializer<Object>) CalendarKeySerializer.instance;
44+
}
4145
}
42-
/* 14-Mar-2014, tatu: Should support @JsonValue, as per #47; but that
43-
* requires extensive introspection, and passing in more information
44-
* to this method.
45-
*/
46-
// If no match, just use default one:
47-
return DEFAULT_KEY_SERIALIZER;
46+
return useDefault ? DEFAULT_KEY_SERIALIZER : null;
47+
}
48+
49+
/**
50+
* @deprecated Since 2.5
51+
*/
52+
@Deprecated
53+
public static JsonSerializer<Object> getStdKeySerializer(JavaType keyType) {
54+
return getStdKeySerializer(null, keyType.getRawClass(), true);
4855
}
4956

57+
public static JsonSerializer<Object> getDefault() {
58+
return DEFAULT_KEY_SERIALIZER;
59+
}
60+
5061
/*
5162
/**********************************************************
5263
/* Standard implementations

src/test/java/com/fasterxml/jackson/failing/TestMapJsonValueKey47.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ public void testMapJsonValueKey()
4141

4242
ObjectMapper mapper = new ObjectMapper();
4343
String json = mapper.writeValueAsString(input);
44-
assertEquals(aposToQuotes("{'3':'true'}"), json);
44+
assertEquals(aposToQuotes("{'3':true}"), json);
4545
}
4646
}

0 commit comments

Comments
 (0)