MapSerializationTest.java
package tools.jackson.databind.ser.jdk;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import tools.jackson.core.*;
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.exc.InvalidDefinitionException;
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.*;
@SuppressWarnings("serial")
public class MapSerializationTest extends DatabindTestUtil
{
// // // Inner types from MapSerializationTest
@JsonSerialize(using=PseudoMapSerializer.class)
public static class PseudoMap extends LinkedHashMap<String,String>
{
public PseudoMap(String... values) {
for (int i = 0, len = values.length; i < len; i += 2) {
put(values[i], values[i+1]);
}
}
}
static class PseudoMapSerializer extends ValueSerializer<Map<String,String>>
{
@Override
public void serialize(Map<String,String> value,
JsonGenerator g, SerializationContext ctxt)
{
// just use standard Map.toString(), output as JSON String
g.writeString(value.toString());
}
}
// [databind#335]
public static class MapOrderingBean {
@JsonPropertyOrder(alphabetic=true)
public LinkedHashMap<String,Integer> map;
public MapOrderingBean(String... keys) {
map = new LinkedHashMap<String,Integer>();
int ix = 1;
for (String key : keys) {
map.put(key, ix++);
}
}
}
// for [databind#691]
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME)
@JsonTypeName("mymap")
public static class MapWithTypedValues extends LinkedHashMap<String,String> { }
@JsonTypeInfo(use = Id.CLASS)
public static class Mixin691 { }
// // // Inner types from MapKeyAnnotationsTest
// [databind#47]
public static class Wat
{
private final String wat;
@JsonCreator
Wat(String wat) {
this.wat = wat;
}
@JsonValue
public String getWat() {
return wat;
}
@Override
public String toString() {
return "(String)[Wat: " + wat + "]";
}
}
static class WatMap extends HashMap<Wat,Boolean> { }
// [databind#943]
public static class UCString {
private String value;
public UCString(String v) {
value = v.toUpperCase();
}
@JsonValue
public String asString() {
return value;
}
}
public enum AbcLC {
A, B, C;
@JsonValue
public String toLC() {
return name().toLowerCase();
}
}
// [databind#2306]
public static class JsonValue2306Key {
@JsonValue
private String id;
public JsonValue2306Key(String id) {
this.id = id;
}
}
// [databind#2871]
public static class Inner2871 {
@JsonKey
String key;
@JsonValue
String value;
Inner2871(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "Inner2871(" + this.key + "," + this.value + ")";
}
}
public static class Outer2871 {
@JsonKey
@JsonValue
Inner2871 inner;
Outer2871(Inner2871 inner) {
this.inner = inner;
}
}
public static class NoKeyOuter {
@JsonValue
Inner2871 inner;
NoKeyOuter(Inner2871 inner) {
this.inner = inner;
}
}
// // // Inner types from MapKeySerializationTest
static class KarlSerializer extends ValueSerializer<String>
{
@Override
public void serialize(String value, JsonGenerator g, SerializationContext ctxt) {
g.writeName("Karl");
}
}
public static class NotKarlBean
{
public Map<String,Integer> map = new HashMap<String,Integer>();
{
map.put("Not Karl", 1);
}
}
public static class KarlBean
{
@JsonSerialize(keyUsing = KarlSerializer.class)
public Map<String,Integer> map = new HashMap<String,Integer>();
{
map.put("Not Karl", 1);
}
}
public enum OuterEnum {
inner;
}
public enum ABCKey {
A, B, C
}
public static class ABCMapWrapper {
public Map<ABCKey,String> stuff = new HashMap<ABCKey,String>();
public ABCMapWrapper() {
stuff.put(ABCKey.B, "bar");
}
}
@JsonSerialize(keyUsing = ABCKeySerializer.class)
public static enum ABCMixin { }
public 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<ABCKey> {
@Override
public void serialize(ABCKey value, JsonGenerator g,
SerializationContext ctxt) {
g.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 g, SerializationContext ctxt) {
g.writeName(_null);
}
}
static class NullValueSerializer extends ValueSerializer<Object>
{
private String _null;
public NullValueSerializer(String s) { _null = s; }
@Override
public void serialize(Object value, JsonGenerator g, SerializationContext ctxt) {
g.writeString(_null);
}
}
static class DefaultKeySerializer extends ValueSerializer<Object>
{
@Override
public void serialize(Object value, JsonGenerator g, SerializationContext ctxt) {
g.writeName("DEFAULT:"+value);
}
}
// // // Inner types from MapSerializationSorted4773Test
public static class IncomparableContainer4773 {
public Map<Currency, String> exampleMap = new HashMap<>();
}
public static class ObjectContainer4773 {
public Map<Object, String> exampleMap = new HashMap<>();
}
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
private final ObjectMapper SORTING_MAPPER = jsonMapperBuilder()
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.build();
// // // Tests from MapSerializationTest
@Test
public void testUsingObjectWriter() throws IOException
{
ObjectWriter w = MAPPER.writerFor(Object.class);
Map<String,Object> map = new LinkedHashMap<String,Object>();
map.put("a", 1);
String json = w.writeValueAsString(map);
assertEquals(a2q("{'a':1}"), json);
}
@Test
public void testMapSerializer() throws IOException
{
assertEquals("\"{a=b, c=d}\"", MAPPER.writeValueAsString(new PseudoMap("a", "b", "c", "d")));
}
// problems with map entries, values
@Test
public void testMapKeySetValuesSerialization() throws IOException
{
Map<String,String> map = new HashMap<String,String>();
map.put("a", "b");
assertEquals("[\"a\"]", MAPPER.writeValueAsString(map.keySet()));
assertEquals("[\"b\"]", MAPPER.writeValueAsString(map.values()));
// TreeMap has similar inner class(es):
map = new TreeMap<String,String>();
map.put("c", "d");
assertEquals("[\"c\"]", MAPPER.writeValueAsString(map.keySet()));
assertEquals("[\"d\"]", MAPPER.writeValueAsString(map.values()));
// and for [JACKSON-533], same for concurrent maps
map = new ConcurrentHashMap<String,String>();
map.put("e", "f");
assertEquals("[\"e\"]", MAPPER.writeValueAsString(map.keySet()));
assertEquals("[\"f\"]", MAPPER.writeValueAsString(map.values()));
}
// sort Map entries by key
@Test
public void testOrderByKey() throws IOException
{
ObjectMapper m = newJsonMapper();
assertFalse(m.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS));
LinkedHashMap<String,Integer> map = new LinkedHashMap<String,Integer>();
map.put("b", 3);
map.put("a", 6);
// by default, no (re)ordering:
assertEquals("{\"b\":3,\"a\":6}", m.writeValueAsString(map));
// but can be changed
ObjectWriter sortingW = m.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
assertEquals("{\"a\":6,\"b\":3}", sortingW.writeValueAsString(map));
}
// related to [databind#1411]
@Test
public void testOrderByWithNulls() throws IOException
{
ObjectWriter sortingW = MAPPER.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
.without(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY);
// 16-Oct-2016, tatu: but mind the null key, if any
Map<String,Integer> mapWithNullKey = new LinkedHashMap<String,Integer>();
mapWithNullKey.put(null, 1);
mapWithNullKey.put("b", 2);
// 16-Oct-2016, tatu: By default, null keys are not accepted...
try {
/*String json =*/ sortingW.writeValueAsString(mapWithNullKey);
//assertEquals(a2q("{'':1,'b':2}"), json);
} catch (DatabindException e) {
verifyException(e, "Null key for a Map not allowed");
}
}
// [Databind#335]
@Test
public void testOrderByKeyViaProperty() throws IOException
{
MapOrderingBean input = new MapOrderingBean("c", "b", "a");
String json = MAPPER.writeValueAsString(input);
assertEquals(a2q("{'map':{'a':3,'b':2,'c':1}}"), json);
}
// [databind#691]
@Test
public void testNullJsonMapping691() throws Exception
{
MapWithTypedValues input = new MapWithTypedValues();
input.put("id", "Test");
input.put("NULL", null);
String json = MAPPER.writeValueAsString(input);
assertEquals(a2q("{'@type':'mymap','id':'Test','NULL':null}"),
json);
}
// [databind#691]
@Test
public void testNullJsonInTypedMap691() throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("NULL", null);
ObjectMapper mapper = jsonMapperBuilder()
.addMixIn(Object.class, Mixin691.class)
.build();
String json = mapper.writeValueAsString(map);
assertEquals("{\"@class\":\"java.util.HashMap\",\"NULL\":null}", json);
}
// [databind#1513]
@Test
public void testConcurrentMaps() throws Exception
{
final ObjectWriter w = MAPPER.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
Map<String,String> input = new ConcurrentSkipListMap<String,String>();
input.put("x", "y");
input.put("a", "b");
String json = w.writeValueAsString(input);
assertEquals(a2q("{'a':'b','x':'y'}"), json);
input = new ConcurrentHashMap<String,String>();
input.put("x", "y");
input.put("a", "b");
json = w.writeValueAsString(input);
assertEquals(a2q("{'a':'b','x':'y'}"), json);
// One more: while not technically concurrent map at all, exhibits same issue
input = new Hashtable<String,String>();
input.put("x", "y");
input.put("a", "b");
json = w.writeValueAsString(input);
assertEquals(a2q("{'a':'b','x':'y'}"), json);
}
// // // Tests from MapKeyAnnotationsTest
// [databind#47]
@Test
public void testMapJsonValueKey47() throws Exception
{
WatMap input = new WatMap();
input.put(new Wat("3"), true);
String json = MAPPER.writeValueAsString(input);
assertEquals(a2q("{'3':true}"), json);
}
// [databind#943]
@Test
public void testDynamicMapKeys() throws Exception
{
Map<Object,Integer> stuff = new LinkedHashMap<Object,Integer>();
stuff.put(AbcLC.B, Integer.valueOf(3));
stuff.put(new UCString("foo"), Integer.valueOf(4));
String json = MAPPER.writeValueAsString(stuff);
assertEquals(a2q("{'b':3,'FOO':4}"), json);
}
// [databind#2306]
@Test
public void testMapKeyWithJsonValue() throws Exception
{
final Map<JsonValue2306Key, String> map = Collections.singletonMap(
new JsonValue2306Key("myId"), "value");
assertEquals(a2q("{'myId':'value'}"),
MAPPER.writeValueAsString(map));
}
// [databind#2871]
@Test
public void testClassAsKey() throws Exception {
Outer2871 outer = new Outer2871(new Inner2871("innerKey", "innerValue"));
Map<Outer2871, String> map = Collections.singletonMap(outer, "value");
String actual = MAPPER.writeValueAsString(map);
assertEquals("{\"innerKey\":\"value\"}", actual);
}
// [databind#2871]
@Test
public void testClassAsValue() throws Exception {
Map<String, Outer2871> mapA = Collections.singletonMap("key", new Outer2871(new Inner2871("innerKey", "innerValue")));
String actual = MAPPER.writeValueAsString(mapA);
assertEquals("{\"key\":\"innerValue\"}", actual);
}
// [databind#2871]
@Test
public void testNoKeyOuter() throws Exception {
Map<String, NoKeyOuter> mapA = Collections.singletonMap("key", new NoKeyOuter(new Inner2871("innerKey", "innerValue")));
String actual = MAPPER.writeValueAsString(mapA);
assertEquals("{\"key\":\"innerValue\"}", actual);
}
// // // Tests from MapKeySerializationTest
@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(ABCKey.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<OuterEnum, Object> outerMap = new HashMap<OuterEnum, Object>();
Map<ABCKey, Map<String, String>> map = new EnumMap<ABCKey, Map<String, String>>(ABCKey.class);
Map<String, String> innerMap = new HashMap<String, String>();
innerMap.put("one", "1");
map.put(ABCKey.A, innerMap);
outerMap.put(OuterEnum.inner, map);
SimpleModule mod = new SimpleModule("test")
.setMixInAnnotation(ABCKey.class, ABCMixin.class)
.addKeySerializer(ABCKey.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);
}
// [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(ABCKey.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<ABCKey,BAR<?>> stuff = new HashMap<ABCKey,BAR<?>>();
stuff.put(ABCKey.B, new BAR<String>("bar"));
String json = mapper.writerFor(new TypeReference<Map<ABCKey,BAR<?>>>() {})
.writeValueAsString(stuff);
assertEquals("{\"xxxB\":\"bar\"}", json);
}
// [databind#838]
@Test
public void testUnWrappedMapWithDefaultType() throws Exception{
SimpleModule mod = new SimpleModule("test");
mod.addKeySerializer(ABCKey.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<ABCKey,String> stuff = new HashMap<ABCKey,String>();
stuff.put(ABCKey.B, "bar");
String json = mapper.writerFor(new TypeReference<Map<ABCKey, 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);
}
// // // Tests from MapSerializationSorted4773Test
// [databind#4773]
@Test
void testSerializationFailureWhenEnabledWithIncomparableKeys()
throws Exception
{
IncomparableContainer4773 entity = new IncomparableContainer4773();
entity.exampleMap.put(Currency.getInstance("GBP"), "GBP_TEXT");
entity.exampleMap.put(Currency.getInstance("AUD"), "AUD_TEXT");
try {
SORTING_MAPPER.writer()
.with(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY)
.writeValueAsString(entity);
fail("Should not pass");
} catch (InvalidDefinitionException e) {
verifyException(e, "Cannot order Map entries by key of incomparable type");
}
}
// [databind#4773]
@Test
void testSerializationWithGenericObjectKeys() throws Exception
{
ObjectContainer4773 entity = new ObjectContainer4773();
entity.exampleMap.put(5, "N_TEXT");
entity.exampleMap.put(1, "GBP_TEXT");
entity.exampleMap.put(3, "T_TEXT");
entity.exampleMap.put(4, "AUD_TEXT");
entity.exampleMap.put(2, "KRW_TEXT");
String jsonResult = SORTING_MAPPER.writeValueAsString(entity);
assertEquals(a2q("{'exampleMap':{" +
"'1':'GBP_TEXT'," +
"'2':'KRW_TEXT'," +
"'3':'T_TEXT'," +
"'4':'AUD_TEXT'," +
"'5':'N_TEXT'}}"), jsonResult);
}
// [databind#4773]
@Test
void testSerWithNullType() throws Exception
{
ObjectContainer4773 entity = new ObjectContainer4773();
entity.exampleMap.put(null, "AUD_TEXT");
try {
SORTING_MAPPER.writer()
.with(SerializationFeature.FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY)
.writeValueAsString(entity);
fail("Should not pass");
} catch (InvalidDefinitionException e) {
verifyException(e, "Cannot order Map entries by key of incomparable type [null]");
}
}
}