TestTypeModifiers.java
package tools.jackson.databind.module;
import java.lang.reflect.Type;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonFormat;
import tools.jackson.core.*;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.Serializers;
import tools.jackson.databind.ser.std.StdSerializer;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.type.*;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("serial")
public class TestTypeModifiers extends DatabindTestUtil
{
private static class ModifierModule extends SimpleModule
{
public ModifierModule() {
super("test", Version.unknownVersion());
}
@Override
public void setupModule(SetupContext context)
{
context.addSerializers(new Serializers.Base() {
@Override
public ValueSerializer<?> findMapLikeSerializer(SerializationConfig config,
MapLikeType type, BeanDescription.Supplier beanDesc, JsonFormat.Value format,
ValueSerializer<Object> keySerializer,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer)
{
if (MapMarker.class.isAssignableFrom(type.getRawClass())) {
return new MyMapSerializer(keySerializer, elementValueSerializer);
}
return null;
}
@Override
public ValueSerializer<?> findCollectionLikeSerializer(SerializationConfig config,
CollectionLikeType type, BeanDescription.Supplier beanDesc, JsonFormat.Value format,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer)
{
if (CollectionMarker.class.isAssignableFrom(type.getRawClass())) {
return new MyCollectionSerializer();
}
return null;
}
});
context.addDeserializers(new SimpleDeserializers() {
@Override
public ValueDeserializer<?> findCollectionLikeDeserializer(CollectionLikeType type,
DeserializationConfig config,
BeanDescription.Supplier beanDescRef, TypeDeserializer elementTypeDeserializer,
ValueDeserializer<?> elementDeserializer)
{
if (CollectionMarker.class.isAssignableFrom(type.getRawClass())) {
return new MyCollectionDeserializer();
}
return null;
}
@Override
public ValueDeserializer<?> findMapLikeDeserializer(MapLikeType type,
DeserializationConfig config,
BeanDescription.Supplier beanDescRef, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, ValueDeserializer<?> elementDeserializer)
{
if (MapMarker.class.isAssignableFrom(type.getRawClass())) {
return new MyMapDeserializer();
}
return null;
}
});
}
}
static class XxxSerializer extends StdSerializer<Object>
{
public XxxSerializer() { super(Object.class); }
@Override
public void serialize(Object value, JsonGenerator g, SerializationContext provider) {
g.writeString("xxx:"+value);
}
}
interface MapMarker<K,V> {
public K getKey();
public V getValue();
}
interface CollectionMarker<V> {
public V getValue();
}
@JsonSerialize(contentUsing=XxxSerializer.class)
static class MyMapLikeType implements MapMarker<String,Integer> {
public String key;
public int value;
public MyMapLikeType() { }
public MyMapLikeType(String k, int v) {
key = k;
value = v;
}
@Override
public String getKey() { return key; }
@Override
public Integer getValue() { return value; }
}
static class MyCollectionLikeType implements CollectionMarker<Integer>
{
public int value;
public MyCollectionLikeType() { }
public MyCollectionLikeType(int v) {
value = v;
}
@Override
public Integer getValue() { return value; }
}
static class MyMapSerializer extends StdSerializer<MapMarker<?,?>>
{
protected final ValueSerializer<Object> _keySerializer;
protected final ValueSerializer<Object> _valueSerializer;
public MyMapSerializer(ValueSerializer<Object> keySer, ValueSerializer<Object> valueSer) {
super(MapMarker.class);
_keySerializer = keySer;
_valueSerializer = valueSer;
}
@Override
public void serialize(MapMarker<?,?> value, JsonGenerator jgen, SerializationContext provider)
{
jgen.writeStartObject();
if (_keySerializer == null) {
jgen.writeName((String) value.getKey());
} else {
_keySerializer.serialize(value.getKey(), jgen, provider);
}
if (_valueSerializer == null) {
jgen.writeNumber(((Number) value.getValue()).intValue());
} else {
_valueSerializer.serialize(value.getValue(), jgen, provider);
}
jgen.writeEndObject();
}
}
static class MyMapDeserializer extends ValueDeserializer<MapMarker<?,?>>
{
@Override
public MapMarker<?,?> deserialize(JsonParser p, DeserializationContext ctxt)
{
if (p.currentToken() != JsonToken.START_OBJECT) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
if (p.nextToken() != JsonToken.PROPERTY_NAME) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
String key = p.currentName();
if (p.nextToken() != JsonToken.VALUE_NUMBER_INT) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
int value = p.getIntValue();
if (p.nextToken() != JsonToken.END_OBJECT) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
return new MyMapLikeType(key, value);
}
}
static class MyCollectionSerializer extends StdSerializer<MyCollectionLikeType>
{
public MyCollectionSerializer() { super(MyCollectionLikeType.class); }
@Override
public void serialize(MyCollectionLikeType value, JsonGenerator g, SerializationContext provider) {
g.writeStartArray();
g.writeNumber(value.value);
g.writeEndArray();
}
}
static class MyCollectionDeserializer extends ValueDeserializer<MyCollectionLikeType>
{
@Override
public MyCollectionLikeType deserialize(JsonParser p, DeserializationContext ctxt) {
if (p.currentToken() != JsonToken.START_ARRAY) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
if (p.nextToken() != JsonToken.VALUE_NUMBER_INT) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
int value = p.getIntValue();
if (p.nextToken() != JsonToken.END_ARRAY) throw new StreamReadException(p, "Wrong token: "+p.currentToken());
return new MyCollectionLikeType(value);
}
}
static class MyTypeModifier extends TypeModifier
{
@Override
public JavaType modifyType(JavaType type, Type jdkType, TypeBindings bindings, TypeFactory typeFactory)
{
if (!type.isContainerType()) { // not 100% required, minor optimization
Class<?> raw = type.getRawClass();
if (raw == MapMarker.class) {
return MapLikeType.upgradeFrom(type, type.containedType(0), type.containedType(1));
}
if (raw == CollectionMarker.class) {
return CollectionLikeType.upgradeFrom(type, type.containedType(0));
}
}
return type;
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
private final ObjectMapper MY_TYPE_MAPPER = jsonMapperBuilder()
.typeFactory(defaultTypeFactory().withModifier(new MyTypeModifier()))
.build();
private final ObjectMapper MAPPER_WITH_MODIFIER = jsonMapperBuilder()
.typeFactory(defaultTypeFactory().withModifier(new MyTypeModifier()))
.addModule(new ModifierModule())
.build();
/**
* Basic test for ensuring that we can get "xxx-like" types recognized.
*/
@Test
public void testMapLikeTypeConstruction() throws Exception
{
JavaType type = MY_TYPE_MAPPER.constructType(MyMapLikeType.class);
assertTrue(type.isMapLikeType());
// also, must have resolved type info
JavaType param = ((MapLikeType) type).getKeyType();
assertNotNull(param);
assertSame(String.class, param.getRawClass());
param = ((MapLikeType) type).getContentType();
assertNotNull(param);
assertSame(Integer.class, param.getRawClass());
}
@Test
public void testMapLikeTypeViaParametric() throws Exception
{
// [databind#2796]: should refine with another call too
JavaType type = MAPPER_WITH_MODIFIER.getTypeFactory().constructParametricType(MapMarker.class,
new Class<?>[] { String.class, Double.class });
assertTrue(type.isMapLikeType());
JavaType param = ((MapLikeType) type).getKeyType();
assertNotNull(param);
assertSame(String.class, param.getRawClass());
param = ((MapLikeType) type).getContentType();
assertNotNull(param);
assertSame(Double.class, param.getRawClass());
}
// [databind#2395] Can trigger problem this way too
// NOTE: oddly enough, seems to ONLY fail
@Test
public void testTypeResolutionForRecursive() throws Exception
{
final ObjectMapper mapper = jsonMapperBuilder()
.typeFactory(defaultTypeFactory().withModifier(new MyTypeModifier()))
.build();
assertNotNull(mapper.readTree("{}"));
}
@Test
public void testCollectionLikeTypeConstruction() throws Exception
{
JavaType type = MY_TYPE_MAPPER.constructType(MyCollectionLikeType.class);
assertTrue(type.isCollectionLikeType());
JavaType param = ((CollectionLikeType) type).getContentType();
assertNotNull(param);
assertSame(Integer.class, param.getRawClass());
}
@Test
public void testCollectionLikeSerialization() throws Exception
{
assertEquals("[19]", MAPPER_WITH_MODIFIER.writeValueAsString(new MyCollectionLikeType(19)));
}
@Test
public void testMapLikeSerialization() throws Exception
{
// Due to custom serializer, should get:
assertEquals("{\"x\":\"xxx:3\"}", MAPPER_WITH_MODIFIER.writeValueAsString(new MyMapLikeType("x", 3)));
}
@Test
public void testCollectionLikeDeserialization() throws Exception
{
MyMapLikeType result = MAPPER_WITH_MODIFIER.readValue("{\"a\":13}", MyMapLikeType.class);
assertEquals("a", result.getKey());
assertEquals(Integer.valueOf(13), result.getValue());
}
@Test
public void testMapLikeDeserialization() throws Exception
{
MyCollectionLikeType result = MAPPER_WITH_MODIFIER.readValue("[-37]", MyCollectionLikeType.class);
assertEquals(Integer.valueOf(-37), result.getValue());
}
}