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> { } /* /********************************************************** /* 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<String,Integer> result = MAPPER.readValue (JSON, new TypeReference<HashMap<Integer,Boolean>>() { }); 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("")); } /* /********************************************************** /* 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"); } } }