blob: 4b56d28013c684f7b880a86db852226af075117b [file] [log] [blame]
package com.fasterxml.jackson.databind.deser.jdk;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
@SuppressWarnings("serial")
public class MapDeserializationTest
extends BaseMapTest
{
static enum Key {
KEY1, KEY2, WHATEVER;
}
static class BrokenMap
extends HashMap<Object,Object>
{
// No default ctor, nor @JsonCreators
public BrokenMap(boolean dummy) { super(); }
}
@JsonDeserialize(using=CustomMapDeserializer.class)
static class CustomMap extends LinkedHashMap<String,String> { }
static class CustomMapDeserializer extends StdDeserializer<CustomMap>
{
public CustomMapDeserializer() { super(CustomMap.class); }
@Override
public CustomMap deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
CustomMap result = new CustomMap();
result.put("x", p.getText());
return result;
}
}
static class KeyType {
protected String value;
private KeyType(String v, boolean bogus) {
value = v;
}
@JsonCreator
public static KeyType create(String v) {
return new KeyType(v, true);
}
}
public static class EnumMapContainer {
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public EnumMap<KeyEnum,ITestType> testTypes;
}
public static class ListContainer {
public List<ITestType> testTypes;
}
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public static interface ITestType { }
public static enum KeyEnum {
A, B
}
public static enum ConcreteType implements ITestType {
ONE, TWO;
}
static class ClassStringMap extends HashMap<Class<?>,String> { }
static class AbstractMapWrapper {
public AbstractMap<String, Integer> values;
}
/*
/**********************************************************
/* Test methods, untyped (Object valued) maps
/**********************************************************
*/
private final ObjectMapper MAPPER = new ObjectMapper();
public void testBigUntypedMap() throws Exception
{
Map<String,Object> map = new LinkedHashMap<String,Object>();
for (int i = 0; i < 1100; ++i) {
if ((i & 1) == 0) {
map.put(String.valueOf(i), Integer.valueOf(i));
} else {
Map<String,Object> map2 = new LinkedHashMap<String,Object>();
map2.put("x", Integer.valueOf(i));
map.put(String.valueOf(i), map2);
}
}
String json = MAPPER.writeValueAsString(map);
Object bound = MAPPER.readValue(json, Object.class);
assertEquals(map, bound);
}
/**
* Let's also try another way to express "gimme a Map" deserialization;
* this time by specifying a Map class, to reduce need to cast
*/
public void testUntypedMap2() throws Exception
{
// to get "untyped" default map-to-map, pass Object.class
String JSON = "{ \"a\" : \"x\" }";
@SuppressWarnings("unchecked")
HashMap<String,Object> result = /*(HashMap<String,Object>)*/ MAPPER.readValue(JSON, HashMap.class);
assertNotNull(result);
assertTrue(result instanceof Map<?,?>);
assertEquals(1, result.size());
assertEquals("x", result.get("a"));
}
/**
* Unit test for [JACKSON-185]
*/
public void testUntypedMap3() throws Exception
{
String JSON = "{\"a\":[{\"a\":\"b\"},\"value\"]}";
Map<?,?> result = MAPPER.readValue(JSON, Map.class);
assertTrue(result instanceof Map<?,?>);
assertEquals(1, result.size());
Object ob = result.get("a");
assertNotNull(ob);
Collection<?> list = (Collection<?>)ob;
assertEquals(2, list.size());
JSON = "{ \"var1\":\"val1\", \"var2\":\"val2\", "
+"\"subvars\": ["
+" { \"subvar1\" : \"subvar2\", \"x\" : \"y\" }, "
+" { \"a\":1 } ]"
+" }"
;
result = MAPPER.readValue(JSON, Map.class);
assertTrue(result instanceof Map<?,?>);
assertEquals(3, result.size());
}
private static final String UNTYPED_MAP_JSON =
"{ \"double\":42.0, \"string\":\"string\","
+"\"boolean\":true, \"list\":[\"list0\"],"
+"\"null\":null }";
static class ObjectWrapperMap extends HashMap<String, ObjectWrapper> { }
public void testSpecialMap() throws IOException
{
final ObjectWrapperMap map = MAPPER.readValue(UNTYPED_MAP_JSON, ObjectWrapperMap.class);
assertNotNull(map);
_doTestUntyped(map);
}
public void testGenericMap() throws IOException
{
final Map<String, ObjectWrapper> map = MAPPER.readValue
(UNTYPED_MAP_JSON,
new TypeReference<Map<String, ObjectWrapper>>() { });
_doTestUntyped(map);
}
private void _doTestUntyped(final Map<String, ObjectWrapper> map)
{
ObjectWrapper w = map.get("double");
assertNotNull(w);
assertEquals(Double.valueOf(42), w.getObject());
assertEquals("string", map.get("string").getObject());
assertEquals(Boolean.TRUE, map.get("boolean").getObject());
assertEquals(Collections.singletonList("list0"), map.get("list").getObject());
assertTrue(map.containsKey("null"));
assertNull(map.get("null"));
assertEquals(5, map.size());
}
// [JACKSON-620]: allow "" to mean 'null' for Maps
public void testFromEmptyString() throws Exception
{
ObjectMapper m = new ObjectMapper();
m.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
Map<?,?> result = m.readValue(quote(""), Map.class);
assertNull(result);
}
/*
/**********************************************************
/* Test methods, typed maps
/**********************************************************
*/
public void testExactStringIntMap() throws Exception
{
// to get typing, must use type reference
String JSON = "{ \"foo\" : 13, \"bar\" : -39, \n \"\" : 0 }";
Map<String,Integer> result = MAPPER.readValue
(JSON, new TypeReference<HashMap<String,Integer>>() { });
assertNotNull(result);
assertEquals(HashMap.class, result.getClass());
assertEquals(3, result.size());
assertEquals(Integer.valueOf(13), result.get("foo"));
assertEquals(Integer.valueOf(-39), result.get("bar"));
assertEquals(Integer.valueOf(0), result.get(""));
assertNull(result.get("foobar"));
assertNull(result.get(" "));
}
/**
* Let's also check that it is possible to do type conversions
* to allow use of non-String Map keys.
*/
public void testIntBooleanMap() throws Exception
{
// to get typing, must use type reference
String JSON = "{ \"1\" : true, \"-1\" : false }";
Map<?,Object> result = MAPPER.readValue
(JSON, new TypeReference<HashMap<Integer,Object>>() { });
assertNotNull(result);
assertEquals(HashMap.class, result.getClass());
assertEquals(2, result.size());
assertEquals(Boolean.TRUE, result.get(Integer.valueOf(1)));
assertEquals(Boolean.FALSE, result.get(Integer.valueOf(-1)));
assertNull(result.get("foobar"));
assertNull(result.get(0));
}
public void testExactStringStringMap() throws Exception
{
// to get typing, must use type reference
String JSON = "{ \"a\" : \"b\" }";
Map<String,Integer> result = MAPPER.readValue
(JSON, new TypeReference<TreeMap<String,String>>() { });
assertNotNull(result);
assertEquals(TreeMap.class, result.getClass());
assertEquals(1, result.size());
assertEquals("b", result.get("a"));
assertNull(result.get("b"));
}
/**
* Unit test that verifies that it's ok to have incomplete
* information about Map class itself, as long as it's something
* we good guess about: for example, <code>Map.Class</code> will
* be replaced by something like <code>HashMap.class</code>,
* if given.
*/
public void testGenericStringIntMap() throws Exception
{
// to get typing, must use type reference; but with abstract type
String JSON = "{ \"a\" : 1, \"b\" : 2, \"c\" : -99 }";
Map<String,Integer> result = MAPPER.readValue
(JSON, new TypeReference<Map<String,Integer>>() { });
assertNotNull(result);
assertTrue(result instanceof Map<?,?>);
assertEquals(3, result.size());
assertEquals(Integer.valueOf(-99), result.get("c"));
assertEquals(Integer.valueOf(2), result.get("b"));
assertEquals(Integer.valueOf(1), result.get("a"));
assertNull(result.get(""));
}
public void testAbstractMapDefault() throws Exception
{
final AbstractMapWrapper result = MAPPER.readValue("{\"values\":{\"foo\":42}}",
AbstractMapWrapper.class);
assertNotNull(result);
assertEquals(LinkedHashMap.class, result.values.getClass());
}
/*
/**********************************************************
/* Test methods, maps with enums
/**********************************************************
*/
public void testEnumMap() throws Exception
{
String JSON = "{ \"KEY1\" : \"\", \"WHATEVER\" : null }";
// to get typing, must use type reference
EnumMap<Key,String> result = MAPPER.readValue
(JSON, new TypeReference<EnumMap<Key,String>>() { });
assertNotNull(result);
assertEquals(EnumMap.class, result.getClass());
assertEquals(2, result.size());
assertEquals("", result.get(Key.KEY1));
// null should be ok too...
assertTrue(result.containsKey(Key.WHATEVER));
assertNull(result.get(Key.WHATEVER));
// plus we have nothing for this key
assertFalse(result.containsKey(Key.KEY2));
assertNull(result.get(Key.KEY2));
}
public void testMapWithEnums() throws Exception
{
String JSON = "{ \"KEY2\" : \"WHATEVER\" }";
// to get typing, must use type reference
Map<Enum<?>,Enum<?>> result = MAPPER.readValue
(JSON, new TypeReference<Map<Key,Key>>() { });
assertNotNull(result);
assertTrue(result instanceof Map<?,?>);
assertEquals(1, result.size());
assertEquals(Key.WHATEVER, result.get(Key.KEY2));
assertNull(result.get(Key.WHATEVER));
assertNull(result.get(Key.KEY1));
}
public void testEnumPolymorphicSerializationTest() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
List<ITestType> testTypesList = new ArrayList<ITestType>();
testTypesList.add(ConcreteType.ONE);
testTypesList.add(ConcreteType.TWO);
ListContainer listContainer = new ListContainer();
listContainer.testTypes = testTypesList;
String json = mapper.writeValueAsString(listContainer);
listContainer = mapper.readValue(json, ListContainer.class);
EnumMapContainer enumMapContainer = new EnumMapContainer();
EnumMap<KeyEnum,ITestType> testTypesMap = new EnumMap<KeyEnum,ITestType>(KeyEnum.class);
testTypesMap.put(KeyEnum.A, ConcreteType.ONE);
testTypesMap.put(KeyEnum.B, ConcreteType.TWO);
enumMapContainer.testTypes = testTypesMap;
json = mapper.writeValueAsString(enumMapContainer);
enumMapContainer = mapper.readValue(json, EnumMapContainer.class);
}
/*
/**********************************************************
/* Test methods, maps with Date
/**********************************************************
*/
public void testDateMap() throws Exception
{
Date date1=new Date(123456000L);
DateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
String JSON = "{ \""+ fmt.format(date1)+"\" : \"\", \""+new Date(0).getTime()+"\" : null }";
HashMap<Date,String> result= MAPPER.readValue
(JSON, new TypeReference<HashMap<Date,String>>() { });
assertNotNull(result);
assertEquals(HashMap.class, result.getClass());
assertEquals(2, result.size());
assertTrue(result.containsKey(date1));
assertEquals("", result.get(new Date(123456000L)));
assertTrue(result.containsKey(new Date(0)));
assertNull(result.get(new Date(0)));
}
/*
/**********************************************************
/* Test methods, maps with various alternative key types
/**********************************************************
*/
public void testCalendarMap() throws Exception
{
// 18-Jun-2015, tatu: Should be safest to use default timezone that mapper would use
TimeZone tz = MAPPER.getSerializationConfig().getTimeZone();
Calendar c = Calendar.getInstance(tz);
c.setTimeInMillis(123456000L);
DateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
String JSON = "{ \""+fmt.format(c.getTime())+"\" : \"\", \""+new Date(0).getTime()+"\" : null }";
HashMap<Calendar,String> result = MAPPER.readValue
(JSON, new TypeReference<HashMap<Calendar,String>>() { });
assertNotNull(result);
assertEquals(HashMap.class, result.getClass());
assertEquals(2, result.size());
assertTrue(result.containsKey(c));
assertEquals("", result.get(c));
c.setTimeInMillis(0);
assertTrue(result.containsKey(c));
assertNull(result.get(c));
}
public void testUUIDKeyMap() throws Exception
{
UUID key = UUID.nameUUIDFromBytes("foobar".getBytes("UTF-8"));
String JSON = "{ \""+key+"\":4}";
Map<UUID,Object> result = MAPPER.readValue(JSON, new TypeReference<Map<UUID,Object>>() { });
assertNotNull(result);
assertEquals(1, result.size());
Object ob = result.keySet().iterator().next();
assertNotNull(ob);
assertEquals(UUID.class, ob.getClass());
assertEquals(key, ob);
}
public void testLocaleKeyMap() throws Exception {
Locale key = Locale.CHINA;
String JSON = "{ \"" + key + "\":4}";
Map<Locale, Object> result = MAPPER.readValue(JSON, new TypeReference<Map<Locale, Object>>() {
});
assertNotNull(result);
assertEquals(1, result.size());
Object ob = result.keySet().iterator().next();
assertNotNull(ob);
assertEquals(Locale.class, ob.getClass());
assertEquals(key, ob);
}
public void testCurrencyKeyMap() throws Exception {
Currency key = Currency.getInstance("USD");
String JSON = "{ \"" + key + "\":4}";
Map<Currency, Object> result = MAPPER.readValue(JSON, new TypeReference<Map<Currency, Object>>() {
});
assertNotNull(result);
assertEquals(1, result.size());
Object ob = result.keySet().iterator().next();
assertNotNull(ob);
assertEquals(Currency.class, ob.getClass());
assertEquals(key, ob);
}
// Test confirming that @JsonCreator may be used with Map Key types
public void testKeyWithCreator() throws Exception
{
// first, key should deserialize normally:
KeyType key = MAPPER.readValue(quote("abc"), KeyType.class);
assertEquals("abc", key.value);
Map<KeyType,Integer> map = MAPPER.readValue("{\"foo\":3}", new TypeReference<Map<KeyType,Integer>>() {} );
assertEquals(1, map.size());
key = map.keySet().iterator().next();
assertEquals("foo", key.value);
}
public void testClassKeyMap() throws Exception {
ClassStringMap map = MAPPER.readValue(aposToQuotes("{'java.lang.String':'foo'}"),
ClassStringMap.class);
assertNotNull(map);
assertEquals(1, map.size());
assertEquals("foo", map.get(String.class));
}
public void testcharSequenceKeyMap() throws Exception {
String JSON = aposToQuotes("{'a':'b'}");
Map<CharSequence,String> result = MAPPER.readValue(JSON, new TypeReference<Map<CharSequence,String>>() { });
assertNotNull(result);
assertEquals(1, result.size());
assertEquals("b", result.get("a"));
}
/*
/**********************************************************
/* Test methods, annotated Maps
/**********************************************************
*/
/**
* Simple test to ensure that @JsonDeserialize.using is
* recognized
*/
public void testMapWithDeserializer() throws Exception
{
CustomMap result = MAPPER.readValue(quote("xyz"), CustomMap.class);
assertEquals(1, result.size());
assertEquals("xyz", result.get("x"));
}
/*
/**********************************************************
/* Error tests
/**********************************************************
*/
public void testMapError() throws Exception
{
try {
Object result = MAPPER.readValue("[ 1, 2 ]",
new TypeReference<Map<String,String>>() { });
fail("Expected an exception, but got result value: "+result);
} catch (JsonMappingException jex) {
verifyException(jex, "START_ARRAY");
}
}
public void testNoCtorMap() throws Exception
{
try {
BrokenMap result = MAPPER.readValue("{ \"a\" : 3 }", BrokenMap.class);
// should never get here; assert added to remove compiler warning
assertNull(result);
} catch (JsonMappingException e) {
// instead, should get this exception:
verifyException(e, "no default constructor found");
}
}
}