Reputation: 839
Something like a simplified Version of @JsonAppend
public class Bean {
@JsonAppend(key = [...], value = [...])
public Map<?, ?> map = new HashMap<>();
}
would be great - any simple way to achieve this?
I've read lot's of SO entries, eg.
but found nothing matching my needs.
The reason for my request is that it is indistinguishable whether some given JSON originated from Map or POJO serialization. If this is necessary (in rare cases), adding a magic extra field to the map would be a simple way to achieve this.
Upvotes: 3
Views: 1868
Reputation: 21152
Great question! Yes, this is (somehow) possible. The following exposed methodology maintains the standard serialization behavior, while adding on top of it annotation-defined key-value pairs.
Create a custom annotation. I'll call it MapAppender
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MapAppender {
String[] keys();
String[] values();
}
As you can see, we define key-value arrays, which will match by index.
We are forced using String
fields instead of the more generic Object
, but that's per annotation design.
Create a custom JsonSerializer<Map>
. I'll call it MapAppenderSerializer
public class MapAppenderSerializer
extends StdSerializer<Map>
implements ContextualSerializer {
private static final long serialVersionUID = 1L;
private final String[] keys;
private final String[] values;
// No-arg constructor required for Jackson
MapAppenderSerializer() {
super(Map.class);
keys = new String[0];
values = new String[0];
}
MapAppenderSerializer(
final String[] keys,
final String[] values) {
super(Map.class);
this.keys = keys;
this.values = values;
}
@Override
public void serialize(
final Map value,
final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException {
// Create a copy Map to avoid touching the original one
final Map hashMap = new HashMap<>(value);
// Add the annotation-specified key-value pairs
for (int i = 0; i < keys.length; i++) {
hashMap.put(keys[i], values[i]);
}
// Serialize the new Map
serializerProvider.defaultSerializeValue(hashMap, jsonGenerator);
}
@Override
public JsonSerializer<?> createContextual(
final SerializerProvider serializerProvider,
final BeanProperty property) {
MapAppender annotation = null;
if (property != null) {
annotation = property.getAnnotation(MapAppender.class);
}
if (annotation != null) {
return new MapAppenderSerializer(annotation.keys(), annotation.values());
}
throw new UnsupportedOperationException("...");
}
}
Now, using your Bean
class example, annotate the Map
field with @MapAppender
and define a custom serializer using @JsonSerialize
public class Bean {
public String simpleField;
@MapAppender(keys = {"test1", "test2"}, values = {"value1", "value2"})
@JsonSerialize(using = MapAppenderSerializer.class)
public Map<Object, Object> simpleMap = new HashMap<>();
}
That's it. Serializing an instance of Bean
final ObjectMapper objectMapper = new ObjectMapper();
final String string = objectMapper.writeValueAsString(new Bean());
results in
{"simpleField":null,"simpleMap":{"test2":"value2","test1":"value1"}}
Another example, having the Map
populated with values prior to serialization
final ObjectMapper objectMapper = new ObjectMapper();
final Bean value = new Bean();
value.simpleMap.put("myKey", "myValue");
final String string = objectMapper.writeValueAsString(value);
results in
{"simpleField":null,"simpleMap":{"test1":"value1","test2":"value2","myKey":"myValue"}}
Upvotes: 5