Deserializing to abstract types with no concrete implementations

2,663 views
Skip to first unread message

Cameron Matthews-Dickson

unread,
May 16, 2014, 4:11:15 PM5/16/14
to jackso...@googlegroups.com
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);
    }

Tatu Saloranta

unread,
May 20, 2014, 1:13:17 AM5/20/14
to jackso...@googlegroups.com
On Fri, May 16, 2014 at 8:11 PM, Cameron Matthews-Dickson <cmatthew...@gmail.com> wrote:
What I'm attempting to do is deserialize JSON using Jackson into types that have no concrete implementations; only dynamic proxies are used.


This sounds like a tricky problem. But first things first: is there a reason why Mr Bean module -- https://github.com/FasterXML/jackson-module-mrbean -- would not work here? It does create actual concrete classes, on-the-fly, so you don't have to.

Other than that I am not sure how doable this is with standard BeanDeserializer: problem is that it really does require something with getters and setters. Although I realize that proxy code does in fact implement actual class with methods from interface and so on.
If you managed to get proxy to give you Class to use, you might actually be able to use that Class as impl type, similar to what Mr Bean does.

-+ Tatu +-

--
You received this message because you are subscribed to the Google Groups "jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
To post to this group, send email to jackso...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Cameron Matthews-Dickson

unread,
May 20, 2014, 10:45:22 AM5/20/14
to jackso...@googlegroups.com
Thanks for the response...

On Tuesday, May 20, 2014 1:13:17 AM UTC-4, Tatu Saloranta wrote:
This sounds like a tricky problem. But first things first: is there a reason why Mr Bean module -- https://github.com/FasterXML/jackson-module-mrbean -- would not work here? It does create actual concrete classes, on-the-fly, so you don't have to.
That's not a bad option.  I had not read up on that module during my investigations.  It does seem, however, to have a potential downside:  If there is any need for anything beyond just straight-forward get-set methods it would not work...If I am correct in this assessment, then I think this would mean Mr. Bean may not be suitable...
 
Other than that I am not sure how doable this is with standard BeanDeserializer: problem is that it really does require something with getters and setters. Although I realize that proxy code does in fact implement actual class with methods from interface and so on.
So, there is no way to tell Jackson/BeanDeserializer to use a factory when it needs to instantiate certain types?  
You say it may not be doable with "standard BeanDeserializer", does that mean you may have an approach using a customized one?
 
If you managed to get proxy to give you Class to use, you might actually be able to use that Class as impl type, similar to what Mr Bean does.
I don't really see that being possible.  

Tatu Saloranta

unread,
May 20, 2014, 3:36:51 PM5/20/14
to jackso...@googlegroups.com
On Tue, May 20, 2014 at 2:45 PM, Cameron Matthews-Dickson <cmatthew...@gmail.com> wrote:
Thanks for the response...

On Tuesday, May 20, 2014 1:13:17 AM UTC-4, Tatu Saloranta wrote:
This sounds like a tricky problem. But first things first: is there a reason why Mr Bean module -- https://github.com/FasterXML/jackson-module-mrbean -- would not work here? It does create actual concrete classes, on-the-fly, so you don't have to.
That's not a bad option.  I had not read up on that module during my investigations.  It does seem, however, to have a potential downside:  If there is any need for anything beyond just straight-forward get-set methods it would not work...If I am correct in this assessment, then I think this would mean Mr. Bean may not be suitable...

Mr Bean will not implement anything beyond simple getters/setters, but abstract class to implement can have default implementations.

Also, contributions for improvements in code generation (possibly just hooks to allow custom materialization additions) would be welcome.

Anyway, it is possible this might not work; just wanted to make sure you were aware of its existence. Especially as your use case sounded somewhat similar.
 
 
Other than that I am not sure how doable this is with standard BeanDeserializer: problem is that it really does require something with getters and setters. Although I realize that proxy code does in fact implement actual class with methods from interface and so on.
So, there is no way to tell Jackson/BeanDeserializer to use a factory when it needs to instantiate certain types?  

Well, ValueInstantiator should work, but it sounded like you were not able to make it work.
But it is definitely the intended mechanism for creating instances of a type.
Once that is done, handling of getters/setters should work properly, similar to how mr bean only handles construction of the class, but not with actual data-binding.
 
You say it may not be doable with "standard BeanDeserializer", does that mean you may have an approach using a customized one?

I meant BeanDeserializer, as a standard deserializer. Custom ones can obviously do more but they won't get much help from rest of Jackson.
 
 
If you managed to get proxy to give you Class to use, you might actually be able to use that Class as impl type, similar to what Mr Bean does.
I don't really see that being possible.  

Ok.

-+ Tatu +-
Reply all
Reply to author
Forward
0 new messages