public class Request
{
public String currency;
}
public class RequestA extends Request
{
public String fieldA;
}
public class RequestB extends Request
{
public String fieldB;
}
public class CustomTypeIdResolver extends TypeIdResolverBase
{
private JavaType superType;
@Override
public void init(JavaType baseType)
{
superType = baseType;
}
@Override
public String idFromValue(Object value)
{
return idFromValueAndType(value, value.getClass());
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType)
{
String typeId = null;
switch (suggestedType.getSimpleName()) {
case "RequestA":
typeId = "A";
break;
case "RequestB":
typeId = "B";
}
return typeId;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
Class<?> subType = null;
switch (id) {
case "A":
subType = RequestA.class;
break;
case "B":
subType = RequestB.class;
}
return context.constructSpecializedType(superType, subType);
}
@Override
public JsonTypeInfo.Id getMechanism()
{
return JsonTypeInfo.Id.CUSTOM;
}
}public class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector
{
@Override
public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config,
AnnotatedClass ac, JavaType baseType)
{
// Preserve default behaviour
TypeResolverBuilder<?> typeResolver = super.findTypeResolver(config, ac, baseType);
if (typeResolver != null ) {
return typeResolver;
}
if (ac.getAnnotated().equals(RequestA.class)) {
typeResolver = new StdTypeResolverBuilder();
typeResolver = typeResolver.init(JsonTypeInfo.Id.CUSTOM, new CustomTypeIdResolver());
typeResolver = typeResolver.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolver.typeProperty("@type");
} else if (ac.getAnnotated().equals(RequestB.class)) {
typeResolver = new StdTypeResolverBuilder();
typeResolver = typeResolver.init(JsonTypeInfo.Id.CUSTOM, new CustomTypeIdResolver());
typeResolver = typeResolver.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolver.typeProperty("@type");
}
return typeResolver;
}
}
@Test
public void test() throws Exception
{
Request r = new Request();
r.currency = "R";
RequestA a = new RequestA();
a.currency = "A";
a.fieldA = "field A";
RequestB b = new RequestB();
b.currency = "B";
b.fieldB = "field B";
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new CustomAnnotationIntrospector());
String contentR = write(mapper, r);
String contentA = write(mapper, a);
String contentB = write(mapper, b);
Request readR = read(mapper, contentR);
RequestA readA = (RequestA) read(mapper, contentA);
RequestB readB = (RequestB) read(mapper, contentB);
assertEquals(r, readR);
assertEquals(a, readA);
assertEquals(b, readB);
}
private static String write(ObjectMapper mapper, Request r) throws Exception
{
String content = mapper.writeValueAsString(r);
System.out.println(content);
return content;
}
private static Request read(ObjectMapper mapper, String value) throws Exception
{
return mapper.readValue(value, Request.class);
}
{"currency":"R"}
{"@type":"A","currency":"A","fieldA":"field A"}
{"@type":"B","currency":"B","fieldB":"field B"}
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "@type" (class cvo.experiments.Request), not marked as ignorable (one known property: "currency"])
at [Source: {"@type":"A","currency":"A","fieldA":"field A"}; line: 1, column: 11] (through reference chain: cvo.experiments.Request["@type"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:62)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:834)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1094)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1470)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1448)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:282)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
at cvo.experiments.MapperTest.read(MapperTest.java:200)
at cvo.experiments.MapperTest.without_annotations_best(MapperTest.java:183)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)public class Request
{
public String currency;
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Request that = (Request) o;
return Objects.equals(currency, that.currency);
}
@Override
public int hashCode()
{
return Objects.hash(currency);
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer("BaseRequest{");
sb.append("currency='").append(currency).append('\'');
sb.append('}');
return sb.toString();
}
}
public class RequestA extends Request
{
public String fieldA;
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
RequestA requestA = (RequestA) o;
return Objects.equals(fieldA, requestA.fieldA);
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), fieldA);
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer("RequestA{");
sb.append("fieldA='").append(fieldA).append('\'');
sb.append(", currency='").append(currency).append('\'');
sb.append('}');
return sb.toString();
}
}
public class RequestB extends Request
{
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
RequestB requestB = (RequestB) o;
return Objects.equals(fieldB, requestB.fieldB);
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), fieldB);
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer("RequestB{");
sb.append("fieldB='").append(fieldB).append('\'');
sb.append(", currency='").append(currency).append('\'');
sb.append('}');
return sb.toString();
}
public String fieldB;
}
public class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector
{
@Override
public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config,
AnnotatedClass ac, JavaType baseType)
{
// Preserve default behaviour
TypeResolverBuilder<?> typeResolver = super.findTypeResolver(config, ac, baseType);
if (typeResolver != null ) {
return typeResolver;
}
// Used to serialise RequestA
if (ac.getAnnotated().equals(RequestA.class)) {
typeResolver = new StdTypeResolverBuilder();
typeResolver = typeResolver.init(JsonTypeInfo.Id.CUSTOM, new CustomTypeIdResolver());
typeResolver = typeResolver.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolver = typeResolver.typeProperty("@type");
typeResolver = typeResolver.defaultImpl(Request.class);
return typeResolver;
}
// Used to serialise RequestB
if (ac.getAnnotated().equals(RequestB.class)) {
typeResolver = new StdTypeResolverBuilder();
typeResolver = typeResolver.init(JsonTypeInfo.Id.CUSTOM, new CustomTypeIdResolver());
typeResolver = typeResolver.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolver = typeResolver.typeProperty("@type");
typeResolver = typeResolver.defaultImpl(Request.class);
}
// Used to serialise Request (base type) and its subtypes
if (ac.getAnnotated().equals(Request.class)) {
typeResolver = new StdTypeResolverBuilder();
// Base type required upon de-serialisation of subtypes
typeResolver = typeResolver.init(JsonTypeInfo.Id.CUSTOM, new CustomTypeIdResolver(baseType));
typeResolver = typeResolver.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolver = typeResolver.typeProperty("@type");
typeResolver = typeResolver.defaultImpl(Request.class);
}
return typeResolver;
}
}
public class CustomTypeIdResolver extends TypeIdResolverBase
{
private JavaType baseType;
public CustomTypeIdResolver()
{
baseType = null;
}
public CustomTypeIdResolver(JavaType baseType)
{
this.baseType = baseType;
}
@Override
public void init(JavaType baseType)
{
// Assumed it will never be called by Jackson
}
@Override
public String idFromValue(Object value)
{
return idFromValueAndType(value, value.getClass());
}
@Override
public String idFromValueAndType(Object value, Class<?> suggestedType)
{
String typeId = null;
switch (suggestedType.getSimpleName()) {
case "RequestA":
typeId = "A";
break;
case "RequestB":
typeId = "B";
}
return typeId;
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
Class<?> subType = null;
switch (id) {
case "A":
subType = RequestA.class;
break;
case "B":
subType = RequestB.class;
}
return context.constructSpecializedType(baseType, subType);
}
@Override
public JsonTypeInfo.Id getMechanism()
{
return JsonTypeInfo.Id.CUSTOM;
}
}
@Test
public void without_annotations_dream() throws Exception
{
Request r = new Request();
r.currency = "R";
RequestA a = new RequestA();
a.currency = "A";
a.fieldA = "field A";
RequestB b = new RequestB();
b.currency = "B";
b.fieldB = "field B";
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new CustomAnnotationIntrospector());
String contentR = write(mapper, r);
String contentA = write(mapper, a);
String contentB = write(mapper, b);
Request readR = read(mapper, contentR);
RequestA readA = (RequestA) read(mapper, contentA);
RequestB readB = (RequestB) read(mapper, contentB);
assertEquals(r, readR);
assertEquals(a, readA);
assertEquals(b, readB);
}
private static String write(ObjectMapper mapper, Request r) throws Exception
{
String content = mapper.writeValueAsString(r);
System.out.println(String.format("write >>> %s", content));
return content;
}
private static Request read(ObjectMapper mapper, String value) throws Exception
{
Request request = mapper.readValue(value, Request.class);
System.out.println(String.format("read <<< %s", request));
return request;
}write >>> {"currency":"R"}
write >>> {"@type":"A","currency":"A","fieldA":"field A"}
write >>> {"@type":"B","currency":"B","fieldB":"field B"}
read <<< BaseRequest{currency='R'}
read <<< RequestA{fieldA='field A', currency='A'}
read <<< RequestB{fieldB='field B', currency='B'}