CustomTypeIdResolverTest.java
package com.fasterxml.jackson.databind.jsontype;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class CustomTypeIdResolverTest extends DatabindTestUtil
{
@JsonTypeInfo(use=Id.CUSTOM, include=As.WRAPPER_OBJECT)
@JsonTypeIdResolver(CustomResolver.class)
static abstract class CustomBean { }
static class CustomBeanImpl extends CustomBean {
public int x;
public CustomBeanImpl() { }
public CustomBeanImpl(int x) { this.x = x; }
}
static class ExtBeanWrapper {
@JsonTypeInfo(use=Id.CUSTOM, include=As.EXTERNAL_PROPERTY, property="type")
@JsonTypeIdResolver(ExtResolver.class)
public ExtBean value;
}
static class CustomResolver extends TestCustomResolverBase {
// yes, static: just for test purposes, not real use
static List<JavaType> initTypes;
public CustomResolver() {
super(CustomBean.class, CustomBeanImpl.class);
}
@Override
public void init(JavaType baseType) {
if (initTypes != null) {
initTypes.add(baseType);
}
}
}
static abstract class ExtBean { }
static class ExtBeanImpl extends ExtBean {
public int y;
public ExtBeanImpl() { }
public ExtBeanImpl(int y) { this.y = y; }
}
static class ExtResolver extends TestCustomResolverBase {
public ExtResolver() {
super(ExtBean.class, ExtBeanImpl.class);
}
}
static class TestCustomResolverBase extends TypeIdResolverBase
{
protected final Class<?> superType;
protected final Class<?> subType;
public TestCustomResolverBase(Class<?> baseType, Class<?> implType) {
superType = baseType;
subType = implType;
}
@Override public Id getMechanism() { return Id.CUSTOM; }
@Override public String idFromValue(Object value) {
if (superType.isAssignableFrom(value.getClass())) {
return "*";
}
return "unknown";
}
@Override
public String idFromValueAndType(Object value, Class<?> type) {
return idFromValue(value);
}
@Override
public void init(JavaType baseType) { }
@Override
public JavaType typeFromId(DatabindContext context, String id)
{
if ("*".equals(id)) {
return context.constructType(subType);
}
return null;
}
@Override
public String idFromBaseType() {
return "xxx";
}
}
// for [databind#1270]
static class Top1270 {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonTypeIdResolver(Resolver1270.class)
public Base1270<?> b;
}
static class Base1270<O extends Poly1Base> {
public O options;
public String val;
}
static abstract class Poly1Base { }
static class Poly1 extends Poly1Base {
public String val;
}
static class Resolver1270 implements TypeIdResolver {
public Resolver1270() { }
@Override
public void init(JavaType baseType) { }
@Override
public String idFromValue(Object value) {
if (value.getClass() == Base1270.class) {
return "poly1";
}
return null;
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
return idFromValue(value);
}
@Override
public String idFromBaseType() {
return null;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
if ("poly1".equals(id)) {
return context.getTypeFactory()
.constructType(new TypeReference<Base1270<Poly1>>() { });
}
return null;
}
@Override
public String getDescForKnownTypeIds() {
return null;
}
@Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CUSTOM;
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
@Test
public void testCustomTypeIdResolver() throws Exception
{
List<JavaType> types = new ArrayList<JavaType>();
CustomResolver.initTypes = types;
String json = MAPPER.writeValueAsString(new CustomBean[] { new CustomBeanImpl(28) });
assertEquals("[{\"*\":{\"x\":28}}]", json);
assertEquals(1, types.size());
assertEquals(CustomBean.class, types.get(0).getRawClass());
types = new ArrayList<JavaType>();
CustomResolver.initTypes = types;
CustomBean[] result = MAPPER.readValue(json, CustomBean[].class);
assertNotNull(result);
assertEquals(1, result.length);
assertEquals(28, ((CustomBeanImpl) result[0]).x);
assertEquals(1, types.size());
assertEquals(CustomBean.class, types.get(0).getRawClass());
}
@Test
public void testCustomWithExternal() throws Exception
{
ExtBeanWrapper w = new ExtBeanWrapper();
w.value = new ExtBeanImpl(12);
String json = MAPPER.writeValueAsString(w);
ExtBeanWrapper out = MAPPER.readValue(json, ExtBeanWrapper.class);
assertNotNull(out);
assertEquals(12, ((ExtBeanImpl) out.value).y);
}
// for [databind#1270]
@Test
public void testPolymorphicTypeViaCustom() throws Exception {
Base1270<Poly1> req = new Base1270<Poly1>();
Poly1 o = new Poly1();
o.val = "optionValue";
req.options = o;
req.val = "some value";
Top1270 top = new Top1270();
top.b = req;
String json = MAPPER.writeValueAsString(top);
JsonNode tree = MAPPER.readTree(json);
assertNotNull(tree.get("b"));
assertNotNull(tree.get("b").get("options"));
assertNotNull(tree.get("b").get("options").get("val"));
// Can we reverse the process? I have some doubts
Top1270 itemRead = MAPPER.readValue(json, Top1270.class);
assertNotNull(itemRead);
assertNotNull(itemRead.b);
}
}