CollectionDeserializationTest.java
package tools.jackson.databind.deser.jdk;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.core.*;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static tools.jackson.databind.testutil.DatabindTestUtil.*;
@SuppressWarnings("serial")
public class CollectionDeserializationTest
{
enum Key {
KEY1, KEY2, WHATEVER;
}
@JsonDeserialize(using=ListDeserializer.class)
static class CustomList extends LinkedList<String> { }
static class ListDeserializer extends StdDeserializer<CustomList>
{
public ListDeserializer() { super(CustomList.class); }
@Override
public CustomList deserialize(JsonParser jp, DeserializationContext ctxt)
{
CustomList result = new CustomList();
result.add(jp.getString());
return result;
}
}
static class XBean {
public int x;
public XBean() { }
public XBean(int x) { this.x = x; }
}
// [databind#199]
static class ListAsIterable {
public Iterable<String> values;
}
// [databind#2251]
static class ListAsAbstract {
public AbstractList<String> values;
}
static class SetAsAbstract {
public AbstractSet<String> values;
}
static class ListAsIterableX {
public Iterable<XBean> nums;
}
static class KeyListBean {
public List<Key> keys;
}
// [databind#828]
@JsonDeserialize(using=SomeObjectDeserializer.class)
static class SomeObject {}
static class SomeObjectDeserializer extends StdDeserializer<SomeObject> {
public SomeObjectDeserializer() { super(SomeObject.class); }
@Override
public SomeObject deserialize(JsonParser p, DeserializationContext ctxt) {
throw new RuntimeException("I want to catch this exception");
}
}
// [databind#3068]: Exception wrapping (or not)
static class MyContainerModel {
@JsonProperty("processor-id")
public String id = "123";
}
static class MyJobModel {
public Map<String, MyContainerModel> containers = Collections.singletonMap("key",
new MyContainerModel());
public int maxChangeLogStreamPartitions = 13;
}
static class CustomException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CustomException(String s) {
super(s);
}
}
// [databind#5522]
@JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
static class CustomNumberList5522 extends ArrayList<Number> { }
@JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
static class CustomStringList5522 extends ArrayList<String> { }
static class CustomClassForNumber5522 {
private CustomNumberList5522 value;
public CustomNumberList5522 getValue() {
return value;
}
public void setValue(CustomNumberList5522 value) {
this.value = value;
}
}
static class CustomClassForString5522 {
private CustomStringList5522 value;
public CustomStringList5522 getValue() {
return value;
}
public void setValue(CustomStringList5522 value) {
this.value = value;
}
}
static class CustomClassForListField5522 {
@JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<String> value;
public List<String> getValue() {
return value;
}
public void setValue(List<String> value) {
this.value = value;
}
}
/*
/**********************************************************************
/* Test methods
/**********************************************************************
*/
private final static ObjectMapper MAPPER = newJsonMapper();
@Test
public void testUntypedList() throws Exception
{
// to get "untyped" default List, pass Object.class
String JSON = "[ \"text!\", true, null, 23 ]";
// Not a guaranteed cast theoretically, but will work:
// (since we know that Jackson will construct an ArrayList here...)
Object value = MAPPER.readValue(JSON, Object.class);
assertNotNull(value);
assertInstanceOf(ArrayList.class, value);
List<?> result = (List<?>) value;
assertEquals(4, result.size());
assertEquals("text!", result.get(0));
assertEquals(Boolean.TRUE, result.get(1));
assertNull(result.get(2));
assertEquals(Integer.valueOf(23), result.get(3));
}
@Test
public void testExactStringCollection() throws Exception
{
// to get typing, must use type reference
String JSON = "[ \"a\", \"b\" ]";
List<String> result = MAPPER.readValue(JSON, new TypeReference<ArrayList<String>>() { });
assertNotNull(result);
assertEquals(ArrayList.class, result.getClass());
assertEquals(2, result.size());
assertEquals("a", result.get(0));
assertEquals("b", result.get(1));
}
@Test
public void testHashSet() throws Exception
{
String JSON = "[ \"KEY1\", \"KEY2\" ]";
EnumSet<Key> result = MAPPER.readValue(JSON, new TypeReference<EnumSet<Key>>() { });
assertNotNull(result);
assertTrue(EnumSet.class.isAssignableFrom(result.getClass()));
assertEquals(2, result.size());
assertTrue(result.contains(Key.KEY1));
assertTrue(result.contains(Key.KEY2));
assertFalse(result.contains(Key.WHATEVER));
}
/// Test to verify that @JsonDeserialize.using works as expected
@Test
public void testCustomDeserializer() throws IOException
{
CustomList result = MAPPER.readValue(q("abc"), CustomList.class);
assertEquals(1, result.size());
assertEquals("abc", result.get(0));
}
// Testing "implicit JSON array" for single-element arrays,
// mostly produced by Jettison, Badgerfish conversions (from XML)
@SuppressWarnings("unchecked")
@Test
public void testImplicitArrays() throws Exception
{
// can't share mapper, custom configs (could create ObjectWriter tho)
ObjectMapper mapper = jsonMapperBuilder()
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
// first with simple scalar types (numbers), with collections
List<Integer> ints = mapper.readValue("4", List.class);
assertEquals(1, ints.size());
assertEquals(Integer.valueOf(4), ints.get(0));
List<String> strings = mapper.readValue(q("abc"), new TypeReference<ArrayList<String>>() { });
assertEquals(1, strings.size());
assertEquals("abc", strings.get(0));
// and arrays:
int[] intArray = mapper.readValue("-7", int[].class);
assertEquals(1, intArray.length);
assertEquals(-7, intArray[0]);
String[] stringArray = mapper.readValue(q("xyz"), String[].class);
assertEquals(1, stringArray.length);
assertEquals("xyz", stringArray[0]);
// and then with Beans:
List<XBean> xbeanList = mapper.readValue("{\"x\":4}", new TypeReference<List<XBean>>() { });
assertEquals(1, xbeanList.size());
assertEquals(XBean.class, xbeanList.get(0).getClass());
Object ob = mapper.readValue("{\"x\":29}", XBean[].class);
XBean[] xbeanArray = (XBean[]) ob;
assertEquals(1, xbeanArray.length);
assertEquals(XBean.class, xbeanArray[0].getClass());
}
// [JACKSON-620]: allow "" to mean 'null' for Maps
@Test
public void testFromEmptyString() throws Exception
{
ObjectReader r = MAPPER.reader(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
List<?> result = r.forType(List.class).readValue(q(""));
assertNull(result);
}
// [databind#161]
@Test
public void testArrayBlockingQueue() throws Exception
{
// ok to skip polymorphic type to get Object
ArrayBlockingQueue<?> q = MAPPER.readValue("[1, 2, 3]", ArrayBlockingQueue.class);
assertNotNull(q);
assertEquals(3, q.size());
assertEquals(Integer.valueOf(1), q.take());
assertEquals(Integer.valueOf(2), q.take());
assertEquals(Integer.valueOf(3), q.take());
}
// [databind#199]
@Test
public void testIterableWithStrings() throws Exception
{
String JSON = "{ \"values\":[\"a\",\"b\"]}";
ListAsIterable w = MAPPER.readValue(JSON, ListAsIterable.class);
assertNotNull(w);
assertNotNull(w.values);
Iterator<String> it = w.values.iterator();
assertTrue(it.hasNext());
assertEquals("a", it.next());
assertEquals("b", it.next());
assertFalse(it.hasNext());
}
@Test
public void testIterableWithBeans() throws Exception
{
String JSON = "{ \"nums\":[{\"x\":1},{\"x\":2}]}";
ListAsIterableX w = MAPPER.readValue(JSON, ListAsIterableX.class);
assertNotNull(w);
assertNotNull(w.nums);
Iterator<XBean> it = w.nums.iterator();
assertTrue(it.hasNext());
XBean xb = it.next();
assertNotNull(xb);
assertEquals(1, xb.x);
xb = it.next();
assertEquals(2, xb.x);
assertFalse(it.hasNext());
}
// for [databind#506]
@Test
public void testArrayIndexForExceptions1() throws Exception
{
try {
MAPPER.readValue("[ \"KEY2\", false ]", Key[].class);
fail("Should not pass");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot deserialize value of type");
verifyException(e, "from Boolean value");
assertEquals(1, e.getPath().size());
assertEquals(1, e.getPath().get(0).getIndex());
}
}
@Test
public void testArrayIndexForExceptions2() throws Exception
{
try {
MAPPER.readValue("[ \"xyz\", { } ]", String[].class);
fail("Should not pass");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot deserialize value of type");
verifyException(e, "from Object value");
assertEquals(1, e.getPath().size());
assertEquals(1, e.getPath().get(0).getIndex());
}
}
@Test
public void testArrayIndexForExceptions3() throws Exception
{
try {
MAPPER.readValue("{\"keys\":[ \"KEY2\", false ]}", KeyListBean.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
verifyException(e, "Cannot deserialize value of type");
verifyException(e, "from Boolean value");
assertEquals(2, e.getPath().size());
// Bean has no index, but has name:
assertEquals(-1, e.getPath().get(0).getIndex());
assertEquals("keys", e.getPath().get(0).getPropertyName());
// and for List, reverse:
assertEquals(1, e.getPath().get(1).getIndex());
assertNull(e.getPath().get(1).getPropertyName());
}
}
// for [databind#828]
@Test
public void testWrapExceptions() throws Exception
{
final ObjectReader wrappingReader = MAPPER
.readerFor(new TypeReference<List<SomeObject>>() {})
.with(DeserializationFeature.WRAP_EXCEPTIONS);
try {
wrappingReader.readValue("[{}]");
} catch (JacksonException exc) {
assertEquals("I want to catch this exception", exc.getOriginalMessage());
} catch (RuntimeException exc) {
fail("The RuntimeException should have been wrapped with a DatabindException, got: "+exc.getClass());
}
final ObjectReader noWrapReader = MAPPER
.readerFor(new TypeReference<List<SomeObject>>() {})
.without(DeserializationFeature.WRAP_EXCEPTIONS);
try {
noWrapReader.readValue("[{}]");
} catch (DatabindException exc) {
fail("It should not have wrapped the RuntimeException.");
} catch (RuntimeException exc) {
assertEquals("I want to catch this exception", exc.getMessage());
}
}
// [databind#2251]
@Test
public void testAbstractListAndSet() throws Exception
{
final String JSON = "{\"values\":[\"foo\", \"bar\"]}";
ListAsAbstract list = MAPPER.readValue(JSON, ListAsAbstract.class);
assertEquals(2, list.values.size());
assertEquals(ArrayList.class, list.values.getClass());
SetAsAbstract set = MAPPER.readValue(JSON, SetAsAbstract.class);
assertEquals(2, set.values.size());
assertEquals(HashSet.class, set.values.getClass());
}
// for [databind#3068]
@Test
public void testWrapExceptions3068() throws Exception
{
final SimpleModule module = new SimpleModule("SimpleModule", Version.unknownVersion())
.addDeserializer(MyContainerModel.class,
new ValueDeserializer<MyContainerModel>() {
@Override
public MyContainerModel deserialize(JsonParser p, DeserializationContext ctxt) {
throw new CustomException("Custom message");
}
});
final ObjectMapper mapper = jsonMapperBuilder()
.addModule(module)
.build();
final String json = mapper.writeValueAsString(new MyJobModel());
// First, verify NO wrapping:
try {
mapper.readerFor(MyJobModel.class)
.without(DeserializationFeature.WRAP_EXCEPTIONS)
.readValue(json);
fail("Should not pass");
} catch (CustomException e) {
verifyException(e, "Custom message");
} catch (JacksonException e) {
fail("Should not have wrapped exception, got: "+e);
}
// and then wrapping
try {
mapper.readerFor(MyJobModel.class)
.with(DeserializationFeature.WRAP_EXCEPTIONS)
.readValue(json);
fail("Should not pass");
} catch (JacksonException e) {
verifyException(e, "Custom message");
Throwable rootC = e.getCause();
assertNotNull(rootC);
assertEquals(CustomException.class, rootC.getClass());
}
}
/*
/**********************************************************
/* Test methods, JDK Collections [databind#1868], [databind#4262]
/**********************************************************
*/
// Round-trip test for singleton collections
@Test
public void testSingletonCollections() throws Exception
{
final TypeReference<List<XBean>> xbeanListType = new TypeReference<List<XBean>>() { };
String json = MAPPER.writeValueAsString(Collections.singleton(new XBean(3)));
Collection<XBean> result = MAPPER.readValue(json, xbeanListType);
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(3, result.iterator().next().x);
json = MAPPER.writeValueAsString(Collections.singletonList(new XBean(28)));
result = MAPPER.readValue(json, xbeanListType);
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(28, result.iterator().next().x);
}
// [databind#1868]: Verify class name serialized as is
@Test
public void testUnmodifiableSet() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.activateDefaultTyping(NoCheckSubTypeValidator.instance,
DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
.build();
Set<String> theSet = Collections.unmodifiableSet(Collections.singleton("a"));
String json = mapper.writeValueAsString(theSet);
assertEquals("[\"java.util.Collections$UnmodifiableSet\",[\"a\"]]", json);
Set<?> result = mapper.readValue(json, Set.class);
assertNotNull(result);
assertEquals(1, result.size());
}
// [databind#4262]: Handle problem of `null`s for `TreeSet`
@Test
public void testNullsWithTreeSet() throws Exception
{
try {
MAPPER.readValue("[ \"acb\", null, 123 ]", TreeSet.class);
fail("Should not pass");
} catch (MismatchedInputException e) {
verifyException(e, "`java.util.Collection` of type ");
verifyException(e, " does not accept `null` values");
}
}
// for [databind#216]
@Test
public void testJava6Types() throws Exception
{
Deque<?> dq = MAPPER.readValue("[1]", Deque.class);
assertNotNull(dq);
assertEquals(1, dq.size());
assertInstanceOf(Deque.class, dq);
NavigableSet<?> ns = MAPPER.readValue("[ true ]", NavigableSet.class);
assertEquals(1, ns.size());
assertInstanceOf(NavigableSet.class, ns);
}
/*
/**********************************************************
/* Test methods, [databind#5522]
/**********************************************************
*/
// [databind#5522]
@Test
public void testCustomNumberCollectionDeserialize5522() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
String jsonValue = """
{
"value": 1
}
""";
CustomClassForNumber5522 result = mapper.readValue(jsonValue, CustomClassForNumber5522.class);
assertThat(result.value)
.hasSize(1)
.containsExactly(1);
}
// [databind#5522]
@Test
public void testCustomStringCollectionDeserialize5522() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
String jsonValue = """
{
"value": "test"
}
""";
CustomClassForString5522 result = mapper.readValue(jsonValue, CustomClassForString5522.class);
assertThat(result.value)
.hasSize(1)
.containsExactly("test");
}
// [databind#5522]
@Test
public void testStringCollectionDeserializeInField5522() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
String jsonValue = """
{
"value": "test"
}
""";
CustomClassForListField5522 result = mapper.readValue(jsonValue, CustomClassForListField5522.class);
assertThat(result.value)
.hasSize(1)
.containsExactly("test");
}
}