blob: 3b28b9d0c40e8f21f59529322b7ada2b571d35f3 [file] [log] [blame]
package com.fasterxml.jackson.databind.deser.jdk;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
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.ContextualDeserializer;
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 UntypedDeserializationTest
extends BaseMapTest
{
static class UCStringDeserializer
extends StdScalarDeserializer<String>
{
public UCStringDeserializer() { super(String.class); }
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return p.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 p, 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 p, DeserializationContext ctxt)
throws IOException
{
ArrayList<Object> list = new ArrayList<Object>();
while (p.nextValue() != JsonToken.END_ARRAY) {
list.add("X"+p.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 YMapDeserializer extends StdDeserializer<Map<String,Object>>
{
public YMapDeserializer() { super(Map.class); }
@Override
public Map<String,Object> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
Map<String,Object> map = new LinkedHashMap<String,Object>();
while (p.nextValue() != JsonToken.END_OBJECT) {
map.put(p.getCurrentName(), "Y"+p.getText());
}
return map;
}
}
static class DelegatingUntyped {
protected Object value;
@JsonCreator // delegating
public DelegatingUntyped(Object v) {
value = v;
}
}
static class WrappedPolymorphicUntyped {
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS)
public Object value;
}
static class WrappedUntyped1460 {
public Object value;
}
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
private final ObjectMapper MAPPER = new ObjectMapper();
@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 = MAPPER.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!
}
@SuppressWarnings("unlikely-arg-type")
public void testUntypedMap() throws Exception
{
// 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));
}
public void testNestedUntypes() throws IOException
{
// 05-Apr-2014, tatu: Odd failures if using shared mapper; so work around:
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));
}
// 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);
}
// Test that exercises non-vanilla variant, with just one simple custom deserializer
public void testNonVanilla() throws IOException
{
SimpleModule m = new SimpleModule("test-module");
m.addDeserializer(String.class, new UCStringDeserializer());
final ObjectMapper mapper = new ObjectMapper()
.registerModule(m);
// Also: since this is now non-vanilla variant, try more alternatives
List<?> l = (List<?>) mapper.readValue("[ true, false, 7, 0.5, \"foo\"]", Object.class);
assertEquals(5, l.size());
assertEquals(Boolean.TRUE, l.get(0));
assertEquals(Boolean.FALSE, l.get(1));
assertEquals(Integer.valueOf(7), l.get(2));
assertEquals(Double.valueOf(0.5), l.get(3));
assertEquals("FOO", l.get(4));
l = (List<?>) mapper.readValue("[ {}, [] ]", Object.class);
assertEquals(2, l.size());
assertTrue(l.get(0) instanceof Map<?,?>);
assertTrue(l.get(1) instanceof List<?>);
ObjectReader rDefault = mapper.readerFor(WrappedPolymorphicUntyped.class);
ObjectReader rAlt = rDefault
.with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS,
DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
WrappedPolymorphicUntyped w;
w = rDefault.readValue(aposToQuotes("{'value':10}"));
assertEquals(Integer.valueOf(10), w.value);
w = rAlt.readValue(aposToQuotes("{'value':10}"));
assertEquals(BigInteger.TEN, w.value);
w = rDefault.readValue(aposToQuotes("{'value':5.0}"));
assertEquals(Double.valueOf(5.0), w.value);
w = rAlt.readValue(aposToQuotes("{'value':5.0}"));
assertEquals(new BigDecimal("5.0"), w.value);
StringBuilder sb = new StringBuilder(100).append("[0");
for (int i = 1; i < 100; ++i) {
sb.append(", ").append(i);
}
sb.append("]");
final String INT_ARRAY_JSON = sb.toString();
// First read as-is, no type wrapping
Object ob = mapper.readerFor(Object.class)
.with(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)
.readValue(INT_ARRAY_JSON);
assertTrue(ob instanceof Object[]);
Object[] obs = (Object[]) ob;
for (int i = 0; i < 100; ++i) {
assertEquals(Integer.valueOf(i), obs[i]);
}
}
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 YMapDeserializer());
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"));
}
public void testNestedUntyped989() throws IOException
{
DelegatingUntyped pojo;
ObjectReader r = MAPPER.readerFor(DelegatingUntyped.class);
pojo = r.readValue("[]");
assertTrue(pojo.value instanceof List);
pojo = r.readValue("[{}]");
assertTrue(pojo.value instanceof List);
pojo = r.readValue("{}");
assertTrue(pojo.value instanceof Map);
pojo = r.readValue("{\"a\":[]}");
assertTrue(pojo.value instanceof Map);
}
public void testUntypedWithJsonArrays() throws Exception
{
// by default we get:
Object ob = MAPPER.readValue("[1]", Object.class);
assertTrue(ob instanceof List<?>);
// but can change to produce Object[]:
MAPPER.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
ob = MAPPER.readValue("[1]", Object.class);
assertEquals(Object[].class, ob.getClass());
}
public void testUntypedIntAsLong() throws Exception
{
final String JSON = aposToQuotes("{'value':3}");
WrappedUntyped1460 w = MAPPER.readerFor(WrappedUntyped1460.class)
.readValue(JSON);
assertEquals(Integer.valueOf(3), w.value);
w = MAPPER.readerFor(WrappedUntyped1460.class)
.with(DeserializationFeature.USE_LONG_FOR_INTS)
.readValue(JSON);
assertEquals(Long.valueOf(3), w.value);
}
}