blob: 0e99fa20c6599ad58170e9328d2dcd84cb2b576f [file] [log] [blame]
package org.codehaus.jackson.map.deser;
import org.codehaus.jackson.*;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.deser.std.StdDeserializer;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import org.codehaus.jackson.type.TypeReference;
public class TestMapDeserialization
extends BaseMapTest
{
/*
/**********************************************************
/* Test classes, enums
/**********************************************************
*/
enum Key {
KEY1, KEY2, WHATEVER;
}
@SuppressWarnings("serial")
static class BrokenMap
extends HashMap<Object,Object>
{
// No default ctor, nor @JsonCreators
public BrokenMap(boolean dummy) { super(); }
}
@SuppressWarnings("serial")
@JsonDeserialize(using=MapDeserializer.class)
static class CustomMap extends LinkedHashMap<String,String> { }
static class MapDeserializer extends StdDeserializer<CustomMap>
{
public MapDeserializer() { super(CustomMap.class); }
@Override
public CustomMap deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException
{
CustomMap result = new CustomMap();
result.put("x", jp.getText());
return result;
}
}
/*
/**********************************************************
/* Test methods, untyped (Object valued) maps
/**********************************************************
*/
public void testUntypedMap() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// to get "untyped" default map-to-map, pass Object.class
String JSON = "{ \"foo\" : \"bar\", \"crazy\" : true, \"null\" : null }";
// Not a guaranteed cast theoretically, but will work:
@SuppressWarnings("unchecked")
Map<String,Object> result = (Map<String,Object>)mapper.readValue(JSON, Object.class);
assertNotNull(result);
assertTrue(result instanceof Map<?,?>);
assertEquals(3, result.size());
assertEquals("bar", result.get("foo"));
assertEquals(Boolean.TRUE, result.get("crazy"));
assertNull(result.get("null"));
// Plus, non existing:
assertNull(result.get("bar"));
assertNull(result.get(3));
}
/**
* 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
{
ObjectMapper mapper = new ObjectMapper();
// 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\"]}";
ObjectMapper m = new ObjectMapper();
Map<?,?> result = m.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 = m.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 }";
@SuppressWarnings("serial")
static class ObjectWrapperMap extends HashMap<String, ObjectWrapper> { }
public void testSpecialMap() throws IOException
{
final ObjectMapper mapper = new ObjectMapper();
final ObjectWrapperMap map = mapper.readValue(UNTYPED_MAP_JSON, ObjectWrapperMap.class);
_doTestUntyped(map);
}
public void testGenericMap() throws IOException
{
final ObjectMapper mapper = new ObjectMapper();
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)
{
assertEquals(Double.valueOf(42), map.get("double").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(DeserializationConfig.Feature.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
{
ObjectMapper mapper = new ObjectMapper();
// 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
{
ObjectMapper mapper = new ObjectMapper();
// 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
{
ObjectMapper mapper = new ObjectMapper();
// 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
{
ObjectMapper mapper = new ObjectMapper();
// 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
{
ObjectMapper mapper = new ObjectMapper();
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
{
ObjectMapper mapper = new ObjectMapper();
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));
}
/*
/**********************************************************
/* Test methods, maps with Date
/**********************************************************
*/
public void testDateMap() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
Date date1=new Date(123456000L);
DateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
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
{
ObjectMapper mapper = new ObjectMapper();
Calendar c = Calendar.getInstance();
c.setTimeInMillis(123456000L);
DateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
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));
}
// [JACKSON-726]
public void testUUIDKeyMap() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
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);
}
/*
/**********************************************************
/* Test methods, annotated Maps
/**********************************************************
*/
/**
* Simple test to ensure that @JsonDeserialize.using is
* recognized
*/
public void testMapWithDeserializer() throws IOException
{
ObjectMapper mapper = new ObjectMapper();
CustomMap result = mapper.readValue(quote("xyz"), CustomMap.class);
assertEquals(1, result.size());
assertEquals("xyz", result.get("x"));
}
/*
/**********************************************************
/* Error tests
/**********************************************************
*/
public void testMapError() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
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
{
ObjectMapper mapper = new ObjectMapper();
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");
}
}
}