MapKeySerializationTest.java
package tools.jackson.databind.ser.jdk;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonValue;
import tools.jackson.core.Base64Variants;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.jsontype.TypeResolverBuilder;
import tools.jackson.databind.jsontype.impl.DefaultTypeResolverBuilder;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;
import static org.junit.jupiter.api.Assertions.*;
public class MapKeySerializationTest extends DatabindTestUtil
{
static class KarlSerializer extends ValueSerializer<String>
{
@Override
public void serialize(String value, JsonGenerator gen, SerializationContext provider) {
gen.writeName("Karl");
}
}
static class NotKarlBean
{
public Map<String,Integer> map = new HashMap<String,Integer>();
{
map.put("Not Karl", 1);
}
}
static class KarlBean
{
@JsonSerialize(keyUsing = KarlSerializer.class)
public Map<String,Integer> map = new HashMap<String,Integer>();
{
map.put("Not Karl", 1);
}
}
static enum Outer {
inner;
}
enum ABC {
A, B, C
}
static class ABCMapWrapper {
public Map<ABC,String> stuff = new HashMap<ABC,String>();
public ABCMapWrapper() {
stuff.put(ABC.B, "bar");
}
}
@JsonSerialize(keyUsing = ABCKeySerializer.class)
static enum ABCMixin { }
static class BAR<T> {
T value;
public BAR(T value) {
this.value = value;
}
@JsonValue
public T getValue() {
return value;
}
@Override
public String toString() {
return this.getClass().getSimpleName()
+ ", value:" + value
;
}
}
static class ABCKeySerializer extends ValueSerializer<ABC> {
@Override
public void serialize(ABC value, JsonGenerator gen,
SerializationContext provider) {
gen.writeName("xxx"+value);
}
}
static class NullKeySerializer extends ValueSerializer<Object>
{
private String _null;
public NullKeySerializer(String s) { _null = s; }
@Override
public void serialize(Object value, JsonGenerator gen, SerializationContext provider) {
gen.writeName(_null);
}
}
static class NullValueSerializer extends ValueSerializer<Object>
{
private String _null;
public NullValueSerializer(String s) { _null = s; }
@Override
public void serialize(Object value, JsonGenerator gen, SerializationContext provider) {
gen.writeString(_null);
}
}
static class DefaultKeySerializer extends ValueSerializer<Object>
{
@Override
public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
g.writeName("DEFAULT:"+value);
}
}
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
final private ObjectMapper MAPPER = newJsonMapper();
@Test
public void testNotKarl() throws Exception {
final String serialized = MAPPER.writeValueAsString(new NotKarlBean());
assertEquals("{\"map\":{\"Not Karl\":1}}", serialized);
}
@Test
public void testKarl() throws Exception {
final String serialized = MAPPER.writeValueAsString(new KarlBean());
assertEquals("{\"map\":{\"Karl\":1}}", serialized);
}
// [databind#75]: caching of KeySerializers
@Test
public void testBoth() throws Exception
{
// Let's NOT use shared one, to ensure caching starts from clean slate
final ObjectMapper mapper = newJsonMapper();
final String value1 = mapper.writeValueAsString(new NotKarlBean());
assertEquals("{\"map\":{\"Not Karl\":1}}", value1);
final String value2 = mapper.writeValueAsString(new KarlBean());
assertEquals("{\"map\":{\"Karl\":1}}", value2);
}
// Test custom key serializer for enum
@Test
public void testCustomForEnum() throws Exception
{
// cannot use shared mapper as we are registering a module
SimpleModule mod = new SimpleModule("test");
mod.addKeySerializer(ABC.class, new ABCKeySerializer());
final ObjectMapper mapper = jsonMapperBuilder()
.addModule(mod)
.build();
String json = mapper.writeValueAsString(new ABCMapWrapper());
assertEquals("{\"stuff\":{\"xxxB\":\"bar\"}}", json);
}
@Test
public void testCustomNullSerializers() throws Exception
{
final SimpleModule mod = new SimpleModule()
.setDefaultNullKeySerializer(new NullKeySerializer("NULL-KEY"))
.setDefaultNullValueSerializer(new NullValueSerializer("NULL"));
final ObjectMapper mapper = jsonMapperBuilder()
.addModule(mod)
.build();
Map<String,Integer> input = new HashMap<>();
input.put(null, 3);
String json = mapper.writeValueAsString(input);
assertEquals("{\"NULL-KEY\":3}", json);
json = mapper.writeValueAsString(new Object[] { 1, null, true });
assertEquals("[1,\"NULL\",true]", json);
}
@Test
public void testCustomEnumInnerMapKey() throws Exception {
Map<Outer, Object> outerMap = new HashMap<Outer, Object>();
Map<ABC, Map<String, String>> map = new EnumMap<ABC, Map<String, String>>(ABC.class);
Map<String, String> innerMap = new HashMap<String, String>();
innerMap.put("one", "1");
map.put(ABC.A, innerMap);
outerMap.put(Outer.inner, map);
SimpleModule mod = new SimpleModule("test")
.setMixInAnnotation(ABC.class, ABCMixin.class)
.addKeySerializer(ABC.class, new ABCKeySerializer())
;
final ObjectMapper mapper = jsonMapperBuilder()
.addModule(mod)
.build();
JsonNode tree = mapper.convertValue(outerMap, JsonNode.class);
JsonNode innerNode = tree.get("inner");
String key = innerNode.propertyNames().iterator().next();
assertEquals("xxxA", key);
}
// 02-Nov-2020, tatu: No more "default key serializer" in 3.0, hence no test
/*
@Test
public void testDefaultKeySerializer() throws Exception
{
final SimpleModule mod = new SimpleModule()
.setDefaultNullKeySerializer(new NullKeySerializer("NULL-KEY"))
// 10-Oct-2019, tatu: Does not exist in 3.0.0 any more./..
.setDefaultKeySerializer(new DefaultKeySerializer());
;
final ObjectMapper mapper = jsonMapperBuilder()
.addModule(mod)
.build();
Map<String,String> map = new HashMap<String,String>();
map.put("a", "b");
assertEquals("{\"DEFAULT:a\":\"b\"}", m.writeValueAsString(map));
}
*/
// [databind#682]
@Test
public void testClassKey() throws Exception
{
Map<Class<?>,Integer> map = new LinkedHashMap<Class<?>,Integer>();
map.put(String.class, 2);
String json = MAPPER.writeValueAsString(map);
assertEquals(a2q("{'java.lang.String':2}"), json);
}
// [databind#838]
@Test
public void testUnWrappedMapWithKeySerializer() throws Exception{
SimpleModule mod = new SimpleModule("test");
mod.addKeySerializer(ABC.class, new ABCKeySerializer());
final ObjectMapper mapper = jsonMapperBuilder()
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY))
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
.addModule(mod)
.build()
;
Map<ABC,BAR<?>> stuff = new HashMap<ABC,BAR<?>>();
stuff.put(ABC.B, new BAR<String>("bar"));
String json = mapper.writerFor(new TypeReference<Map<ABC,BAR<?>>>() {})
.writeValueAsString(stuff);
assertEquals("{\"xxxB\":\"bar\"}", json);
}
// [databind#838]
@Test
public void testUnWrappedMapWithDefaultType() throws Exception{
SimpleModule mod = new SimpleModule("test");
mod.addKeySerializer(ABC.class, new ABCKeySerializer());
TypeResolverBuilder<?> typer = new DefaultTypeResolverBuilder(
NoCheckSubTypeValidator.instance,
DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY, JsonTypeInfo.Id.NAME, null)
.typeIdVisibility(true);
ObjectMapper mapper = jsonMapperBuilder()
.addModule(mod)
.setDefaultTyping(typer)
.build();
Map<ABC,String> stuff = new HashMap<ABC,String>();
stuff.put(ABC.B, "bar");
String json = mapper.writerFor(new TypeReference<Map<ABC, String>>() {})
.writeValueAsString(stuff);
assertEquals("{\"@type\":\"HashMap\",\"xxxB\":\"bar\"}", json);
}
// [databind#1552]
@Test
public void testMapsWithBinaryKeys() throws Exception
{
byte[] binary = new byte[] { 1, 2, 3, 4, 5 };
// First, using wrapper
MapWrapper<byte[], String> input = new MapWrapper<>(binary, "stuff");
String expBase64 = Base64Variants.MIME.encode(binary);
assertEquals(a2q("{'map':{'"+expBase64+"':'stuff'}}"),
MAPPER.writeValueAsString(input));
// and then dynamically..
Map<byte[],String> map = new LinkedHashMap<>();
map.put(binary, "xyz");
assertEquals(a2q("{'"+expBase64+"':'xyz'}"),
MAPPER.writeValueAsString(map));
}
// [databind#1679]
@Test
public void testMapKeyRecursion1679() throws Exception
{
Map<Object, Object> objectMap = new HashMap<Object, Object>();
objectMap.put(new Object(), "foo");
String json = MAPPER.writeValueAsString(objectMap);
assertNotNull(json);
}
}