package com.fasterxml.jackson.databind.jsontype; import java.util.*; 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.type.TypeFactory; public class TestCustomTypeIdResolver extends BaseMapTest { @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 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 TypeFactory.defaultInstance().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 { 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>() { }); } return null; } @Override public String getDescForKnownTypeIds() { return null; } @Override public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.CUSTOM; } } /* /********************************************************** /* Unit tests /********************************************************** */ private final ObjectMapper MAPPER = objectMapper(); public void testCustomTypeIdResolver() throws Exception { List types = new ArrayList(); 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(); 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()); } 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] public void testPolymorphicTypeViaCustom() throws Exception { Base1270 req = new Base1270(); 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); } }