| package com.fasterxml.jackson.databind.deser; |
| |
| import java.io.*; |
| import java.util.*; |
| |
| import com.fasterxml.jackson.annotation.JsonTypeInfo.As; |
| import com.fasterxml.jackson.core.*; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; |
| import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
| import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; |
| import com.fasterxml.jackson.databind.module.SimpleModule; |
| |
| /** |
| * Unit tests for verifying "raw" (or "untyped") data binding from JSON to JDK objects; |
| * one that only uses core JDK types; wrappers, Maps and Lists. |
| */ |
| @SuppressWarnings("serial") |
| public class TestUntypedDeserialization |
| extends BaseMapTest |
| { |
| static class UCStringDeserializer |
| extends StdScalarDeserializer<String> |
| { |
| public UCStringDeserializer() { super(String.class); } |
| |
| @Override |
| public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { |
| return jp.getText().toUpperCase(); |
| } |
| } |
| |
| static class CustomNumberDeserializer |
| extends StdScalarDeserializer<Number> |
| { |
| protected final Integer value; |
| |
| public CustomNumberDeserializer(int nr) { |
| super(Number.class); |
| value = nr; |
| } |
| |
| @Override |
| public Number deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { |
| return value; |
| } |
| } |
| |
| // Let's make this Contextual, to tease out cyclic resolution issues, if any |
| static class ListDeserializer extends StdDeserializer<List<Object>> |
| implements ContextualDeserializer |
| { |
| public ListDeserializer() { super(List.class); } |
| |
| @Override |
| public List<Object> deserialize(JsonParser jp, DeserializationContext ctxt) |
| throws IOException |
| { |
| ArrayList<Object> list = new ArrayList<Object>(); |
| while (jp.nextValue() != JsonToken.END_ARRAY) { |
| list.add("X"+jp.getText()); |
| } |
| return list; |
| } |
| |
| @Override |
| public JsonDeserializer<?> createContextual(DeserializationContext ctxt, |
| BeanProperty property) throws JsonMappingException |
| { |
| // For now, we just need to access "untyped" deserializer; not use it. |
| |
| /*JsonDeserializer<Object> ob = */ |
| ctxt.findContextualValueDeserializer(ctxt.constructType(Object.class), property); |
| return this; |
| } |
| } |
| |
| static class MapDeserializer extends StdDeserializer<Map<String,Object>> |
| { |
| public MapDeserializer() { super(Map.class); } |
| |
| @Override |
| public Map<String,Object> deserialize(JsonParser jp, DeserializationContext ctxt) |
| throws IOException |
| { |
| Map<String,Object> map = new LinkedHashMap<String,Object>(); |
| while (jp.nextValue() != JsonToken.END_OBJECT) { |
| map.put(jp.getCurrentName(), "Y"+jp.getText()); |
| } |
| return map; |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Test methods |
| /********************************************************** |
| */ |
| |
| @SuppressWarnings("unchecked") |
| public void testSampleDoc() throws Exception |
| { |
| final String JSON = SAMPLE_DOC_JSON_SPEC; |
| |
| /* To get "untyped" Mapping (to Maps, Lists, instead of beans etc), |
| * we'll specify plain old Object.class as the target. |
| */ |
| Object root = new ObjectMapper().readValue(JSON, Object.class); |
| |
| assertType(root, Map.class); |
| Map<?,?> rootMap = (Map<?,?>) root; |
| assertEquals(1, rootMap.size()); |
| Map.Entry<?,?> rootEntry = rootMap.entrySet().iterator().next(); |
| assertEquals("Image", rootEntry.getKey()); |
| Object image = rootEntry.getValue(); |
| assertType(image, Map.class); |
| Map<?,?> imageMap = (Map<?,?>) image; |
| assertEquals(5, imageMap.size()); |
| |
| Object value = imageMap.get("Width"); |
| assertType(value, Integer.class); |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_WIDTH), value); |
| |
| value = imageMap.get("Height"); |
| assertType(value, Integer.class); |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_HEIGHT), value); |
| |
| assertEquals(SAMPLE_SPEC_VALUE_TITLE, imageMap.get("Title")); |
| |
| // Another Object, "thumbnail" |
| value = imageMap.get("Thumbnail"); |
| assertType(value, Map.class); |
| Map<?,?> tnMap = (Map<?,?>) value; |
| assertEquals(3, tnMap.size()); |
| |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_HEIGHT), tnMap.get("Height")); |
| // for some reason, width is textual, not numeric... |
| assertEquals(SAMPLE_SPEC_VALUE_TN_WIDTH, tnMap.get("Width")); |
| assertEquals(SAMPLE_SPEC_VALUE_TN_URL, tnMap.get("Url")); |
| |
| // And then number list, "IDs" |
| value = imageMap.get("IDs"); |
| assertType(value, List.class); |
| List<Object> ids = (List<Object>) value; |
| assertEquals(4, ids.size()); |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID1), ids.get(0)); |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID2), ids.get(1)); |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID3), ids.get(2)); |
| assertEquals(Integer.valueOf(SAMPLE_SPEC_VALUE_TN_ID4), ids.get(3)); |
| |
| // and that's all folks! |
| } |
| |
| public void testNestedUntypes() throws IOException |
| { |
| // 05-Apr-2014, tatu: Odd failures if using shared mapper; so work around: |
| final ObjectMapper mapper = new ObjectMapper(); |
| Object root = mapper.readValue(aposToQuotes("{'a':3,'b':[1,2]}"), |
| Object.class); |
| assertTrue(root instanceof Map<?,?>); |
| Map<?,?> map = (Map<?,?>) root; |
| assertEquals(2, map.size()); |
| assertEquals(Integer.valueOf(3), map.get("a")); |
| Object ob = map.get("b"); |
| assertTrue(ob instanceof List<?>); |
| List<?> l = (List<?>) ob; |
| assertEquals(2, l.size()); |
| assertEquals(Integer.valueOf(2), l.get(1)); |
| } |
| |
| // [JACKSON-839]: allow 'upgrade' of big integers into Long, BigInteger |
| public void testObjectSerializeWithLong() throws IOException |
| { |
| final ObjectMapper mapper = new ObjectMapper(); |
| mapper.enableDefaultTyping(DefaultTyping.JAVA_LANG_OBJECT, As.PROPERTY); |
| final long VALUE = 1337800584532L; |
| |
| String serialized = "{\"timestamp\":"+VALUE+"}"; |
| // works fine as node |
| JsonNode deserialized = mapper.readTree(serialized); |
| assertEquals(VALUE, deserialized.get("timestamp").asLong()); |
| // and actually should work in Maps too |
| Map<?,?> deserMap = mapper.readValue(serialized, Map.class); |
| Number n = (Number) deserMap.get("timestamp"); |
| assertNotNull(n); |
| assertSame(Long.class, n.getClass()); |
| assertEquals(Long.valueOf(VALUE), n); |
| } |
| |
| public void testUntypedWithCustomScalarDesers() throws IOException |
| { |
| SimpleModule m = new SimpleModule("test-module"); |
| m.addDeserializer(String.class, new UCStringDeserializer()); |
| m.addDeserializer(Number.class, new CustomNumberDeserializer(13)); |
| final ObjectMapper mapper = new ObjectMapper() |
| .registerModule(m); |
| |
| Object ob = mapper.readValue("{\"a\":\"b\", \"nr\":1 }", Object.class); |
| assertTrue(ob instanceof Map); |
| Object value = ((Map<?,?>) ob).get("a"); |
| assertNotNull(value); |
| assertTrue(value instanceof String); |
| assertEquals("B", value); |
| |
| value = ((Map<?,?>) ob).get("nr"); |
| assertNotNull(value); |
| assertTrue(value instanceof Number); |
| assertEquals(Integer.valueOf(13), value); |
| } |
| |
| public void testUntypedWithListDeser() throws IOException |
| { |
| SimpleModule m = new SimpleModule("test-module"); |
| m.addDeserializer(List.class, new ListDeserializer()); |
| final ObjectMapper mapper = new ObjectMapper() |
| .registerModule(m); |
| |
| // And then list... |
| Object ob = mapper.readValue("[1, 2, true]", Object.class); |
| assertTrue(ob instanceof List<?>); |
| List<?> l = (List<?>) ob; |
| assertEquals(3, l.size()); |
| assertEquals("X1", l.get(0)); |
| assertEquals("X2", l.get(1)); |
| assertEquals("Xtrue", l.get(2)); |
| } |
| |
| public void testUntypedWithMapDeser() throws IOException |
| { |
| SimpleModule m = new SimpleModule("test-module"); |
| m.addDeserializer(Map.class, new MapDeserializer()); |
| final ObjectMapper mapper = new ObjectMapper() |
| .registerModule(m); |
| |
| // And then list... |
| Object ob = mapper.readValue("{\"a\":true}", Object.class); |
| assertTrue(ob instanceof Map<?,?>); |
| Map<?,?> map = (Map<?,?>) ob; |
| assertEquals(1, map.size()); |
| assertEquals("Ytrue", map.get("a")); |
| } |
| } |