GenericTypeTest.java
package tools.jackson.databind.type;
import java.io.Serializable;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for generic type handling in serialization/deserialization:
* bounded generics, generic fields in subtypes, local types, type aliases,
* and polymorphic collection types.
*/
@SuppressWarnings("serial")
public class GenericTypeTest extends DatabindTestUtil
{
/*
/**********************************************************
/* Helper types for bounded generics [JACKSON-778], [databind#537]
/**********************************************************
*/
static class Range<E extends Comparable<E>> implements Serializable
{
protected E start, end;
public Range(){ }
public Range(E start, E end) {
this.start = start;
this.end = end;
}
public E getEnd() { return end; }
public void setEnd(E e) { end = e; }
public E getStart() { return start; }
public void setStart(E s) {
start = s;
}
}
static class DoubleRange extends Range<Double> {
public DoubleRange() { }
public DoubleRange(Double s, Double e) { super(s, e); }
}
static class BoundedWrapper<A extends Serializable>
{
public List<A> values;
}
static class IntBean implements Serializable
{
public int x;
}
static class IntBeanWrapper<T extends IntBean> {
public T wrapped;
}
// Types for [JACKSON-778]
static class Document {}
static class Row {}
static class RowWithDoc<D extends Document> extends Row {
@JsonProperty("d") D d;
}
static class ResultSet<R extends Row> {
@JsonProperty("rows") List<R> rows;
}
static class ResultSetWithDoc<D extends Document> extends ResultSet<RowWithDoc<D>> {}
static class MyDoc extends Document {}
// [databind#537]
interface AnnotatedValue<E> {
public String getAnnotation();
public E getValue();
}
static class AnnotatedValueSimple<E>
implements AnnotatedValue<E>
{
protected E value;
protected AnnotatedValueSimple() { }
public AnnotatedValueSimple(E v) { value = v; }
@Override
public String getAnnotation() { return null; }
@Override
public E getValue() { return value; }
}
static class CbFailing<E extends AnnotatedValue<ID>, ID>
{
private E item;
public CbFailing(E item) {
this.item = item;
}
public E getItem() {
return item;
}
public ID getId() {
return item.getValue();
}
}
/*
/**********************************************************
/* Helper types for generic fields in subtypes [JACKSON-677], [JACKSON-887]
/**********************************************************
*/
// [JACKSON-677]
static class Result677<T> {
public static class Success677<K> extends Result677<K> {
public K value;
public Success677() { }
public Success677(K k) { value = k; }
}
}
// [JACKSON-887]
static abstract class BaseType<T> {
public T value;
public final static class SubType<T extends Number> extends BaseType<T>
{
}
}
/*
/**********************************************************
/* Helper types for local type resolution [databind#609]
/**********************************************************
*/
static class EntityContainer {
RuleForm entity;
@SuppressWarnings("unchecked")
public <T extends RuleForm> T getEntity() { return (T) entity; }
public <T extends RuleForm> void setEntity(T e) { entity = e; }
}
static class RuleForm {
public int value;
public RuleForm() { }
public RuleForm(int v) { value = v; }
}
/*
/**********************************************************
/* Helper types for type aliases [databind#743]
/**********************************************************
*/
public static abstract class Base743<T> {
public T inconsequential = null;
}
public static abstract class BaseData743<T> {
public T dataObj;
}
public static class Child743 extends Base743<Long> {
public static class ChildData extends BaseData743<List<String>> { }
}
/*
/**********************************************************
/* Helper types for polymorphic collection [databind#936]
/**********************************************************
*/
static class StringyList<T extends Serializable> implements Collection<T> {
private Collection<T> _stuff;
@JsonCreator
public StringyList(Collection<T> src) {
_stuff = new ArrayList<T>(src);
}
public StringyList() {
_stuff = new ArrayList<T>();
}
@Override
public boolean add(T arg) {
return _stuff.add(arg);
}
@Override
public boolean addAll(Collection<? extends T> args) {
return _stuff.addAll(args);
}
@Override
public void clear() {
_stuff.clear();
}
@Override
public boolean contains(Object arg) {
return _stuff.contains(arg);
}
@Override
public boolean containsAll(Collection<?> args) {
return _stuff.containsAll(args);
}
@Override
public boolean isEmpty() {
return _stuff.isEmpty();
}
@Override
public Iterator<T> iterator() {
return _stuff.iterator();
}
@Override
public boolean remove(Object arg) {
return _stuff.remove(arg);
}
@Override
public boolean removeAll(Collection<?> args) {
return _stuff.removeAll(args);
}
@Override
public boolean retainAll(Collection<?> args) {
return _stuff.retainAll(args);
}
@Override
public int size() {
return _stuff.size();
}
@Override
public Object[] toArray() {
return _stuff.toArray();
}
@Override
public <X> X[] toArray(X[] arg) {
return _stuff.toArray(arg);
}
}
/*
/**********************************************************
/* Test methods, bounded generics
/**********************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
@Test
public void testLowerBound() throws Exception
{
IntBeanWrapper<?> result = MAPPER.readValue("{\"wrapped\":{\"x\":3}}",
IntBeanWrapper.class);
assertNotNull(result);
assertEquals(IntBean.class, result.wrapped.getClass());
assertEquals(3, result.wrapped.x);
}
// Test related to type bound handling problem within [JACKSON-190]
@Test
public void testBounded() throws Exception
{
BoundedWrapper<IntBean> result = MAPPER.readValue
("{\"values\":[ {\"x\":3} ] } ", new TypeReference<BoundedWrapper<IntBean>>() {});
List<?> list = result.values;
assertEquals(1, list.size());
Object ob = list.get(0);
assertEquals(IntBean.class, ob.getClass());
assertEquals(3, result.values.get(0).x);
}
@Test
public void testGenericsComplex() throws Exception
{
DoubleRange in = new DoubleRange(-0.5, 0.5);
String json = MAPPER.writeValueAsString(in);
DoubleRange out = MAPPER.readValue(json, DoubleRange.class);
assertNotNull(out);
assertEquals(-0.5, out.start);
assertEquals(0.5, out.end);
}
// [JACKSON-778]
@Test
public void testIssue778() throws Exception
{
String json = "{\"rows\":[{\"d\":{}}]}";
final TypeReference<?> typeRef = new TypeReference<ResultSetWithDoc<MyDoc>>() {};
// First, verify type introspection:
JavaType type = MAPPER.getTypeFactory().constructType(typeRef);
JavaType resultSetType = type.findSuperType(ResultSet.class);
assertNotNull(resultSetType);
assertEquals(1, resultSetType.containedTypeCount());
JavaType rowType = resultSetType.containedType(0);
assertNotNull(rowType);
assertEquals(RowWithDoc.class, rowType.getRawClass());
assertEquals(1, rowType.containedTypeCount());
JavaType docType = rowType.containedType(0);
assertEquals(MyDoc.class, docType.getRawClass());
ResultSetWithDoc<MyDoc> rs = MAPPER.readValue(json, type);
Document d = rs.rows.iterator().next().d;
assertEquals(MyDoc.class, d.getClass());
}
// [databind#537]
@Test
public void testCrossReferencingGenericBounds() throws Exception
{
AnnotatedValueSimple<Integer> item = new AnnotatedValueSimple<Integer>(5);
CbFailing<AnnotatedValueSimple<Integer>, Integer> codebook = new CbFailing<AnnotatedValueSimple<Integer>, Integer>(item);
String json = MAPPER.writeValueAsString(codebook);
assertNotNull(json);
}
/*
/**********************************************************
/* Test methods, generic fields in subtypes [JACKSON-677], [JACKSON-887]
/**********************************************************
*/
// [JACKSON-677]
@Test
public void testGenericFieldInSubtype677() throws Exception
{
JavaType t677 = MAPPER.constructType(Result677.Success677.class);
assertNotNull(t677);
Result677.Success677<Integer> s = new Result677.Success677<Integer>(Integer.valueOf(4));
String json = MAPPER.writeValueAsString(s);
assertEquals("{\"value\":4}", json);
}
// [JACKSON-887]
@Test
public void testInnerTypeWithBounds() throws Exception
{
BaseType.SubType<?> r = MAPPER.readValue("{}", BaseType.SubType.class);
assertNotNull(r);
}
/*
/**********************************************************
/* Test methods, local type resolution [databind#609]
/**********************************************************
*/
// [databind#609]
@Test
public void testLocalPartialType609() throws Exception {
EntityContainer input = new EntityContainer();
input.entity = new RuleForm(12);
String json = MAPPER.writeValueAsString(input);
EntityContainer output = MAPPER.readValue(json, EntityContainer.class);
assertEquals(12, output.getEntity().value);
}
/*
/**********************************************************
/* Test methods, type aliases [databind#743]
/**********************************************************
*/
// [databind#743]
@Test
public void testAliasResolutionIssue743() throws Exception
{
String s3 = "{\"dataObj\" : [ \"one\", \"two\", \"three\" ] }";
Child743.ChildData d = MAPPER.readValue(s3, Child743.ChildData.class);
assertNotNull(d.dataObj);
assertEquals(3, d.dataObj.size());
}
/*
/**********************************************************
/* Test methods, polymorphic collection [databind#936]
/**********************************************************
*/
// [databind#936]
@Test
public void testPolymorphicWithOverride() throws Exception
{
JavaType type = MAPPER.getTypeFactory().constructCollectionType(StringyList.class, String.class);
StringyList<String> list = new StringyList<String>();
list.add("value 1");
list.add("value 2");
String serialized = MAPPER.writeValueAsString(list);
StringyList<String> deserialized = MAPPER.readValue(serialized, type);
assertNotNull(deserialized);
}
}