Search code examples
javajacksonhashmaplistenerlinkedhashmap

Jackson won't map to a customized listener map object?


So I've been developing a small LinkedHashMap extension class for use in one of my projects, where I've edited it to have a value change listener in the put method. Heres my map class:

    static class MapWithListeners<K, V> extends LinkedHashMap<K, V> {
        private final LinkedHashMap<K, V> delegate;
        public static final String UPDATE_EVT = "update";

        public MapWithListeners() {
            this.delegate = new LinkedHashMap<>();
        }

        public MapWithListeners(LinkedHashMap<K, V> delegate) {
            this.delegate = delegate;
        }

        private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            changeSupport.addPropertyChangeListener(listener);
        }

        public void removePropertyChangeListener(PropertyChangeListener listener) {
            changeSupport.removePropertyChangeListener(listener);
        }

        protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
            changeSupport.firePropertyChange(propertyName, oldValue, newValue);
        }

        @Override
        public V put(K var1, V var2) {
            V oldValue = delegate.put(var1, var2);
            firePropertyChange(UPDATE_EVT, oldValue == null ? null : new AbstractMap.SimpleEntry<>(var1, oldValue),
                               new AbstractMap.SimpleEntry<>(var1, var2));
            return oldValue;
        }
    }

The problem is, im trying to map an object to an instance of this map class with:

ObjectMapper mapper = new ObjectMapper();
MapWithListeners<String, Object> map = mapper.convertValue(mainObj, new TypeReference<MapWithListeners<String, Object>>() {
        });

And the result is an empty map. I've tried doing this with just a regular LinkedHashMap and it mostly works the way I need it to, but it forfeits the value change listeners, which I also need. I'm assuming I did something wrong in my MapWithListeners class, but cant figure out what that is. Thanks in advance for any help with this!

EDIT: I've found that it was necessary to change my static class to an abstract class, basically like: abstract class MapWithListeners<K,V> extends LinkedHashMap<K,V> implements Map<K,V>

Then configure my mapper with an abstract type mapping module, like:

SimpleModule module = new SimpleModule().addAbstractTypeMapping(Map.class, MapWithListeners.class);
mapper.registerModule(module);

However, getting this far has returned an error being hit on the convertValue line, which says:

java.lang.IllegalArgumentException: Cannot find a deserializer for non-concrete Map type [map type; class com.invoiceeditor.POJOEditor$MapWithListeners, [simple type, class java.lang.String] -> [simple type, class java.lang.Object]]

Any thoughts?


Solution

  • Just give the following a try: Give a little bit more type-support to the Jackson mapper by constructing a custom MapType. Thereafter you can use it for your conversion.

    I've implemented the following and it does the trick and the IllegalArgumentException mentioned in your post is gone:

    MapType javaType = mapper.getTypeFactory().constructMapType(MapWithListeners.class, String.class, Object.class);
    MapWithListeners<String, Object> map = mapper.convertValue(mainObj, javaType);