Skip to content

Adds support for @JsonKey annotation #2905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,23 @@ public PropertyName findNameForSerialization(Annotated a) {
return null;
}

/**
* Method for checking whether given method has an annotation
* that suggests the return value of annotated method
* should be used as "the key" of the object instance; usually
* serialized as a primitive value such as String or number.
*
* @return {@link Boolean#TRUE} if such annotation is found and is not disabled;
* {@link Boolean#FALSE} if disabled annotation (block) is found (to indicate
* accessor is definitely NOT to be used "as value"); or `null` if no
* information found.
*
* @since TODO
*/
public Boolean hasAsKey(MapperConfig<?> config, Annotated a) {
return null;
}

/**
* Method for checking whether given method has an annotation
* that suggests that the return value of annotated method
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/BeanDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,19 @@ public boolean isNonStaticInnerClass() {
/**********************************************************
*/

/**
* Method for locating accessor (readable field, or "getter" method)
* that has
* {@link com.fasterxml.jackson.annotation.JsonKey} annotation,
* if any. If multiple ones are found,
* an error is reported by throwing {@link IllegalArgumentException}
*
* @since TODO
*/
public AnnotatedMember findJsonKeyAccessor() {
return null;
}

/**
* Method for locating accessor (readable field, or "getter" method)
* that has
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ public List<BeanPropertyDefinition> findProperties() {
return _properties();
}

@Override
public AnnotatedMember findJsonKeyAccessor() {
return (_propCollector == null) ? null
: _propCollector.getJsonKeyAccessor();
}

@Override
@Deprecated // since 2.9
public AnnotatedMethod findJsonValueMethod() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,15 @@ public PropertyName findNameForSerialization(Annotated a)
return null;
}

@Override
public Boolean hasAsKey(MapperConfig<?> config, Annotated a) {
JsonKey ann = _findAnnotation(a, JsonKey.class);
if (ann == null) {
return null;
}
return ann.value();
}

@Override // since 2.9
public Boolean hasAsValue(Annotated a) {
JsonValue ann = _findAnnotation(a, JsonValue.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonCreator;

import com.fasterxml.jackson.annotation.JsonKey;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.*;

import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
Expand Down Expand Up @@ -112,7 +114,12 @@ public class POJOPropertiesCollector
protected LinkedList<AnnotatedMember> _anySetterField;

/**
* Method(s) marked with 'JsonValue' annotation
* Accessors (field or "getter" method annotated with {@link JsonKey}
*/
protected LinkedList<AnnotatedMember> _jsonKeyAccessors;

/**
*Accessors (field or "getter" method) annotated with {@link JsonValue}
*<p>
* NOTE: before 2.9, was `AnnotatedMethod`; with 2.9 allows fields too
*/
Expand Down Expand Up @@ -192,6 +199,23 @@ public Map<Object, AnnotatedMember> getInjectables() {
return _injectables;
}

public AnnotatedMember getJsonKeyAccessor() {
if (!_collected) {
collectAll();
}
// If @JsonKey defined, must have a single one
if (_jsonKeyAccessors != null) {
if (_jsonKeyAccessors.size() > 1) {
reportProblem("Multiple 'as-key' properties defined (%s vs %s)",
_jsonKeyAccessors.get(0),
_jsonKeyAccessors.get(1));
}
// otherwise we won't greatly care
return _jsonKeyAccessors.get(0);
}
return null;
}

/**
* @since 2.9
*/
Expand Down Expand Up @@ -421,6 +445,13 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);

for (AnnotatedField f : _classDef.fields()) {
// @JsonKey?
if (Boolean.TRUE.equals(ai.hasAsKey(_config, f))) {
if (_jsonKeyAccessors == null) {
_jsonKeyAccessors = new LinkedList<>();
}
_jsonKeyAccessors.add(f);
}
// @JsonValue?
if (Boolean.TRUE.equals(ai.hasAsValue(f))) {
if (_jsonValueAccessors == null) {
Expand Down Expand Up @@ -646,6 +677,14 @@ protected void _addGetterMethod(Map<String, POJOPropertyBuilder> props,
_anyGetters.add(m);
return;
}
// @JsonKey?
if (Boolean.TRUE.equals(ai.hasAsKey(_config, m))) {
if (_jsonKeyAccessors == null) {
_jsonKeyAccessors = new LinkedList<>();
}
_jsonKeyAccessors.add(m);
return;
}
// @JsonValue?
if (Boolean.TRUE.equals(ai.hasAsValue(m))) {
if (_jsonValueAccessors == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,19 +228,31 @@ public JsonSerializer<Object> createKeySerializer(SerializerProvider ctxt,
ser = StdKeySerializers.getStdKeySerializer(config, keyType.getRawClass(), false);
// As per [databind#47], also need to support @JsonValue
if (ser == null) {
AnnotatedMember am = beanDesc.findJsonValueAccessor();
if (am != null) {
final Class<?> rawType = am.getRawType();
JsonSerializer<?> delegate = StdKeySerializers.getStdKeySerializer(config,
rawType, true);
AnnotatedMember keyAm = beanDesc.findJsonKeyAccessor();
if (keyAm != null) {
final Class<?> rawType = keyAm.getRawType();
JsonSerializer<?> delegate = createKeySerializer(ctxt, config.constructType(rawType), null);
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(am.getMember(),
ClassUtil.checkAndFixAccess(keyAm.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
// null -> no TypeSerializer for key-serializer use case
ser = new JsonValueSerializer(am, null, delegate);
} else {
ser = StdKeySerializers.getFallbackKeySerializer(config, keyType.getRawClass());
ser = new JsonValueSerializer(keyAm, null, delegate);
}
if (ser == null) {
AnnotatedMember am = beanDesc.findJsonValueAccessor();
if (am != null) {
final Class<?> rawType = am.getRawType();
JsonSerializer<?> delegate = StdKeySerializers.getStdKeySerializer(config,
rawType, true);
if (config.canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(am.getMember(),
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
ser = new JsonValueSerializer(am, null, delegate);
} else {
ser = StdKeySerializers.getFallbackKeySerializer(config, keyType.getRawClass());
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.fasterxml.jackson.databind.jsontype;

import java.util.Collections;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonKey;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

public class MapSerializingTest {
class Inner {
@JsonKey
String key;

@JsonValue
String value;

Inner(String key, String value) {
this.key = key;
this.value = value;
}

public String toString() {
return "Inner(" + this.key + "," + this.value + ")";
}

}

class Outer {
@JsonKey
@JsonValue
Inner inner;

Outer(Inner inner) {
this.inner = inner;
}

}

class NoKeyOuter {
@JsonValue
Inner inner;

NoKeyOuter(Inner inner) {
this.inner = inner;
}
}

@Test
public void testClassAsKey() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Outer outer = new Outer(new Inner("innerKey", "innerValue"));
Map<Outer, String> map = Collections.singletonMap(outer, "value");
String actual = mapper.writeValueAsString(map);
Assert.assertEquals("{\"innerKey\":\"value\"}", actual);
}

@Test
public void testClassAsValue() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String, Outer> mapA = Collections.singletonMap("key", new Outer(new Inner("innerKey", "innerValue")));
String actual = mapper.writeValueAsString(mapA);
Assert.assertEquals("{\"key\":\"innerValue\"}", actual);
}

@Test
public void testNoKeyOuter() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String, NoKeyOuter> mapA = Collections.singletonMap("key", new NoKeyOuter(new Inner("innerKey", "innerValue")));
String actual = mapper.writeValueAsString(mapA);
Assert.assertEquals("{\"key\":\"innerValue\"}", actual);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what the code does right now; this I'm not sure if this is appropriate.

}
}