What I'm attempting to do is deserialize JSON using Jackson into types that have no concrete implementations; only dynamic proxies are used.
I would also like the deserialization to respect any Jackson/JAXB annotations that may be defined for the methods of the interfaces.
The proxies are backed by a Map, so I was hoping to just write a custom deserializer that would deserialize to a Map and then use the factory to create the proxy for the proper type.
I have tried a number of approaches (these seemed the most promising :-) ):
- Install custom SimpleDeserializers that overrides findBeanDeserializer() to create an instance of custom deserializer with interface type information - I was able to get this to work, except for finding a way to leverage BeanDeserializer to determine property types, annotations, etc, etc
- Use a BeanDeserializerModifier that overrides modifyDeserializer() to create custom deserializer with type and BeanDeserializer instance
This led to a couple problems:
First, BeanDeserializerFactory.buildBeanDeserializer only creates a BeanDeserializer if the type is a) concrete, or b) has a ValueInstantiator.
So, I attempted to create a ValueInstantiator which got me to the point of having a BeanDeserializer created, however, after that point the ValueInstantiator wasn't being invoked and I'm not sure what else to try...
I think I have tried almost all the "pieces", I just don't know how, or if it's even possible, to put them together to do what I am looking to do. Or, if I'm taking completely the wrong approach
Any help/direction/advice would be appreciated...
Below is a stripped down example of what I've tried...
Example interfaces:
private static interface SomeInterface {
SomeInterface getChild();
void setChild(SomeInterface child);
int getId();
void setId(int id);
}
private static interface SpecializedSomeInterface extends SomeInterface {
int getValue();
void setValue(int value);
}
Example factory and invocation handler:
private static class SomeInterfaceFactory implements InvocationHandler {
public static SomeInterface newInstance(Map<String, Object> map) {
return (SomeInterface) Proxy.newProxyInstance(SomeInterfaceFactory.class.getClassLoader(),
new Class<?>[] {SomeInterface.class}, new SomeInterfaceFactory(map));
}
private final Map<String, Object> map;
SomeInterfaceFactory(Map<String, Object> map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
String methodName = method.getName();
String fieldName = BeanUtils.getPropertyName(methodName);
if(methodName.startsWith("get"))
return map.get(fieldName);
else if(methodName.startsWith("set"))
return map.put(fieldName, args[0]);
return null;
}
}
Some of the Jackson configuration approaches attempted:
private static class MyModule extends SimpleModule {
MyModule() {
StdDelegatingSerializer serializer = new StdDelegatingSerializer(new StdConverter<SomeInterface, Map<String, Object>>() {
@Override
public Map<String, Object> convert(SomeInterface val) {
SomeInterfaceFactory wrapper = (SomeInterfaceFactory) Proxy.getInvocationHandler(val);
return wrapper.map;
}
});
addSerializer(SomeInterface.class, serializer);
SimpleDeserializers deserializers = new SimpleDeserializers() {
@Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type, DeserializationConfig config,
BeanDescription desc) throws JsonMappingException {
if(SomeInterface.class.isAssignableFrom(type.getRawClass())) {
// XXX: Can't create here since there is no access to the BeanDeserializer
// return new SomeInterfaceDeserializer((Class<? extends SomeInterface>) type.getRawClass());
}
return super.findBeanDeserializer(type, config, desc);
}
};
setDeserializers(deserializers);
setDeserializerModifier(new BeanDeserializerModifier() {
@SuppressWarnings("unchecked")
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription desc, JsonDeserializer<?> deser) {
if(SomeInterface.class.isAssignableFrom(desc.getBeanClass())) {
/*
* XXX: Problem: 'deser' is not a BeanSerializer since
* SomeInterface is abstract. Tried to use ValueInstantiator,
* which solved this problem, but wasn't able to get any further
*/
return new SomeInterfaceDeserializer((Class<? extends SomeInterface>) desc.getBeanClass(), (BeanDeserializer) deser);
}
return super.modifyDeserializer(config, desc, deser);
}
});
// Needed so BeanDeserializerFactory will instantiate BeanDeserializer
setValueInstantiators(new SimpleValueInstantiators() {
@Override
public ValueInstantiator findValueInstantiator(DeserializationConfig config, BeanDescription desc,
ValueInstantiator instantiator) {
if(SomeInterface.class.isAssignableFrom(desc.getBeanClass())) {
return new StdValueInstantiator(config, desc.getBeanClass()) {
@Override
public boolean canCreateUsingDefault() {
return true;
}
@Override
public Object createUsingDefault(DeserializationContext context) throws IOException, JsonProcessingException {
return super.createUsingDefault(context);
}
};
}
return super.findValueInstantiator(config, desc, instantiator);
}
});
}
}
private static class SomeInterfaceDeserializer extends StdDeserializer<SomeInterface> {
private final BeanDeserializer delegate;
protected SomeInterfaceDeserializer(Class<?> type, BeanDeserializer delegate) {
super(type);
this.delegate = delegate;
}
@Override
public SomeInterface deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
/*
* XXX: Problem: Not sure if it's possible to get BeanDeserializer to deserialize to another type
*/
// Map<String, Object> map = (Map<String, Object>) delegate.deserialize(parser, context);
// Map<String, Object> map = delegate.deserializeWithType(parser, context, ...);
// Almost works, but doesn't respect bean property types, etc
// JsonDeserializer<?> d = findDeserializer(context, context.getTypeFactory().constructMapType(Map.class, String.class, Object.class), null);
// d.deserialize(parser, context);
return SomeInterfaceFactory.newInstance(map);
}
}
Simple main method to try it out:
public static void main(String[] args) throws IOException {
SomeInterface parent = SomeInterfaceFactory.newInstance(new HashMap<String, Object>());
parent.setId(5);
SomeInterface child = SomeInterfaceFactory.newInstance(new HashMap<String, Object>());
child.setId(10);
parent.setChild(child);
ObjectMapper mapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT)
.registerModule(new MyModule());
String json = mapper.writeValueAsString(parent);
System.out.println(json);
SomeInterface clone = mapper.readValue(json, SomeInterface.class);
}