You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2015/07/28 11:51:52 UTC

incubator-johnzon git commit: JOHNZON-50 enhancing both mode to support merging of annotations, field still but method is read - and doesnt fully overwrite - if field doesnt have the information, JOHNZON-48 adding Karl Grosse tests for @Converter for ite

Repository: incubator-johnzon
Updated Branches:
  refs/heads/master 1ea4fd8c3 -> 70a77af7f


JOHNZON-50 enhancing both mode to support merging of annotations, field still but method is read - and doesnt fully overwrite - if field doesnt have the information, JOHNZON-48 adding Karl Grosse tests for @Converter for item type of collections, enhancing his patch to support collections and eagerly enum converter caching


Project: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/commit/70a77af7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/tree/70a77af7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/diff/70a77af7

Branch: refs/heads/master
Commit: 70a77af7f4271e06f6295d5b6b50070ae0e12d73
Parents: 1ea4fd8
Author: Romain Manni-Bucau <rm...@starbucks.com>
Authored: Tue Jul 28 02:51:24 2015 -0700
Committer: Romain Manni-Bucau <rm...@starbucks.com>
Committed: Tue Jul 28 02:51:44 2015 -0700

----------------------------------------------------------------------
 .../org/apache/johnzon/mapper/Converter.java    |   8 +
 .../java/org/apache/johnzon/mapper/Mapper.java  |  61 +++---
 .../apache/johnzon/mapper/MapperBuilder.java    |  34 ++--
 .../mapper/access/FieldAndMethodAccessMode.java |  69 ++++++-
 .../johnzon/mapper/converter/EnumConverter.java |  16 +-
 .../johnzon/mapper/reflection/Mappings.java     | 146 ++++++++++++++-
 .../org/apache/johnzon/mapper/EnumTest.java     | 185 +++++++++++++++++++
 .../johnzon/mapper/ObjectConverterTest.java     | 152 +++++++++++++++
 8 files changed, 619 insertions(+), 52 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
index 3c36273..6439853 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
@@ -18,7 +18,15 @@
  */
 package org.apache.johnzon.mapper;
 
+import java.lang.reflect.Type;
+
 public interface Converter<T> {
     String toString(T instance);
     T fromString(String text);
+
+    // for generic converters it allows to explicitely provide the converted type (ex: enum converter)
+    // typically useful when generic type get resolved to a TypeVariable
+    interface TypeAccess {
+        Type type();
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
index fb2ec78..e733af0 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
@@ -85,7 +85,7 @@ public class Mapper {
 
     // CHECKSTYLE:OFF
     public Mapper(final JsonReaderFactory readerFactory, final JsonGeneratorFactory generatorFactory,
-                  final boolean doClose, final Map<Class<?>, Converter<?>> converters,
+                  final boolean doClose, final Map<Type, Converter<?>> converters,
                   final int version, final Comparator<String> attributeOrder, final boolean skipNull, final boolean skipEmptyArray,
                   final AccessMode accessMode, final boolean hiddenConstructorSupported, final boolean useConstructors,
                   final boolean treatByteArrayAsBase64,
@@ -96,7 +96,7 @@ public class Mapper {
         this.close = doClose;
         this.converters = new ConcurrentHashMap<Type, Converter<?>>(converters);
         this.version = version;
-        this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors, version, converters);
+        this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors, version, this.converters);
         this.skipNull = skipNull;
         this.skipEmptyArray = skipEmptyArray;
         this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
@@ -361,6 +361,7 @@ public class Mapper {
             generator = writeValue(generator, val.getClass(),
                     getter.primitive, getter.array,
                     getter.collection, getter.map,
+                    getter.itemConverter,
                     getterEntry.getKey(),
                     val);
         }
@@ -389,7 +390,7 @@ public class Mapper {
             final boolean collection = clazz || primitive || array ? false : Collection.class.isAssignableFrom(valueClass);
             final boolean map = clazz || primitive || array || collection ? false : Map.class.isAssignableFrom(valueClass);
             generator = writeValue(generator, valueClass,
-                    primitive, array, collection, map,
+                    primitive, array, collection, map, null /* TODO? */,
                     key == null ? "null" : key.toString(), value);
         }
         return generator;
@@ -398,6 +399,7 @@ public class Mapper {
     private JsonGenerator writeValue(final JsonGenerator generator, final Class<?> type,
                                      final boolean primitive, final boolean array,
                                      final boolean collection, final boolean map,
+                                     final Converter itemConverter,
                                      final String key, final Object value) throws InvocationTargetException, IllegalAccessException {
         if (array) {
             final int length = Array.getLength(value);
@@ -413,13 +415,14 @@ public class Mapper {
 
             JsonGenerator gen = generator.writeStartArray(key);
             for (int i = 0; i < length; i++) {
-                gen = writeItem(gen, Array.get(value, i));
+                final Object o = Array.get(value, i);
+                gen = writeItem(gen, itemConverter != null ? itemConverter.toString(o) : o);
             }
             return gen.writeEnd();
         } else if (collection) {
             JsonGenerator gen = generator.writeStartArray(key);
             for (final Object o : Collection.class.cast(value)) {
-                gen = writeItem(gen, o);
+                gen = writeItem(gen, itemConverter != null ? itemConverter.toString(o) : o);
             }
             return gen.writeEnd();
         } else if (map) {
@@ -431,7 +434,7 @@ public class Mapper {
         } else {
             final Converter<?> converter = findConverter(type);
             if (converter != null) {
-                return writeValue(generator, String.class, true, false, false, false, key,
+                return writeValue(generator, String.class, true, false, false, false, null, key,
                         doConvertFrom(value, (Converter<Object>) converter));
             }
             return doWriteObjectBody(generator.writeStartObject(key), value).writeEnd();
@@ -490,7 +493,7 @@ public class Mapper {
             throw new UnsupportedOperationException("type " + genericType + " not supported");
         }
         try {
-            return mapCollection(mapping, reader.readArray());
+            return mapCollection(mapping, reader.readArray(), null);
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
@@ -515,7 +518,7 @@ public class Mapper {
             throw new UnsupportedOperationException("type " + genericType + " not supported");
         }
         try {
-            return mapCollection(mapping, reader.readArray());
+            return mapCollection(mapping, reader.readArray(), null);
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
@@ -537,7 +540,7 @@ public class Mapper {
 
     private <T> T[] mapArray(final Class<T> clazz, final JsonReader reader) {
         try {
-            return (T[]) buildArrayWithComponentType(reader.readArray(), clazz);
+            return (T[]) buildArrayWithComponentType(reader.readArray(), clazz, null);
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
@@ -583,7 +586,7 @@ public class Mapper {
                         }
 
                         for (final Map.Entry<String, JsonValue> value : object.entrySet()) {
-                            map.put(convertTo(keyType, value.getKey()), toObject(value.getValue(), fieldArgTypes[1]));
+                            map.put(convertTo(keyType, value.getKey()), toObject(value.getValue(), fieldArgTypes[1], null));
                         }
                         return map;
                     }
@@ -604,7 +607,7 @@ public class Mapper {
             final JsonValue jsonValue = object.get(setter.getKey());
             final Mappings.Setter value = setter.getValue();
             final AccessMode.Writer setterMethod = value.writer;
-            final Object convertedValue = toValue(jsonValue, value.converter, value.paramType);
+            final Object convertedValue = toValue(jsonValue, value.converter, value.itemConverter, value.paramType);
 
             if (convertedValue != null) {
                 setterMethod.write(t, convertedValue);
@@ -614,9 +617,9 @@ public class Mapper {
         return t;
     }
 
-    private Object toValue(final JsonValue jsonValue, final Converter<?> converter, final Type type) throws Exception {
+    private Object toValue(final JsonValue jsonValue, final Converter<?> converter, final Converter<?> itemConverter, final Type type) throws Exception {
         return converter == null ?
-                toObject(jsonValue, type) : jsonValue.getValueType() == ValueType.STRING ?
+                toObject(jsonValue, type, itemConverter) : jsonValue.getValueType() == ValueType.STRING ?
                 converter.fromString(JsonString.class.cast(jsonValue).getString()) :
                 converter.fromString(jsonValue.toString());
     }
@@ -624,12 +627,14 @@ public class Mapper {
     private Object[] createParameters(final Mappings.ClassMapping mapping, final JsonObject object) throws Exception {
         final Object[] objects = new Object[mapping.constructorParameters.length];
         for (int i = 0; i < mapping.constructorParameters.length; i++) {
-            objects[i] = toValue(object.get(mapping.constructorParameters[i]), mapping.constructorParameterConverters[i], mapping.constructorParameterTypes[i]);
+            objects[i] = toValue(
+                object.get(mapping.constructorParameters[i]), mapping.constructorParameterConverters[i],
+                mapping.constructorItemParameterConverters[i], mapping.constructorParameterTypes[i]);
         }
         return objects;
     }
 
-    private Object toObject(final JsonValue jsonValue, final Type type) throws Exception {
+    private Object toObject(final JsonValue jsonValue, final Type type, final Converter<?> itemConverter) throws Exception {
         if (jsonValue == null || jsonValue == JsonValue.NULL) {
             return null;
         }
@@ -671,7 +676,7 @@ public class Mapper {
         if (JsonObject.class.isInstance(jsonValue)) {
             return buildObject(type, JsonObject.class.cast(jsonValue));
         } else if (JsonArray.class.isInstance(jsonValue)) {
-            return buildArray(type, JsonArray.class.cast(jsonValue));
+            return buildArray(type, JsonArray.class.cast(jsonValue), itemConverter);
         } else if (JsonNumber.class.isInstance(jsonValue)) {
 
             final JsonNumber number = JsonNumber.class.cast(jsonValue);
@@ -708,36 +713,41 @@ public class Mapper {
                 return number.bigDecimalValue();
             }
         } else if (JsonString.class.isInstance(jsonValue) || Object.class == type) {
-            return convertTo(Class.class.cast(type), JsonString.class.cast(jsonValue).getString());
+            final String string = JsonString.class.cast(jsonValue).getString();
+            if (itemConverter == null) {
+                return convertTo(Class.class.cast(type), string);
+            } else {
+                return itemConverter.fromString(string);
+            }
         }
 
         throw new MapperException("Unable to parse " + jsonValue + " to " + type);
     }
 
-    private Object buildArray(final Type type, final JsonArray jsonArray) throws Exception {
+    private Object buildArray(final Type type, final JsonArray jsonArray, final Converter<?> itemConverter) throws Exception {
         if (Class.class.isInstance(type)) {
             final Class clazz = Class.class.cast(type);
             if (clazz.isArray()) {
                 final Class<?> componentType = clazz.getComponentType();
-                return buildArrayWithComponentType(jsonArray, componentType);
+                return buildArrayWithComponentType(jsonArray, componentType, itemConverter);
             }
         }
 
         if (ParameterizedType.class.isInstance(type)) {
             final Mappings.CollectionMapping mapping = mappings.findCollectionMapping(ParameterizedType.class.cast(type));
             if (mapping != null) {
-                return mapCollection(mapping, jsonArray);
+                return mapCollection(mapping, jsonArray, itemConverter);
             }
         }
 
         if (Object.class == type) {
-            return buildArray(ANY_LIST, jsonArray);
+            return buildArray(ANY_LIST, jsonArray, null);
         }
 
         throw new UnsupportedOperationException("type " + type + " not supported");
     }
 
-    private <T> Collection<T> mapCollection(final Mappings.CollectionMapping mapping, final JsonArray jsonArray) throws Exception {
+    private <T> Collection<T> mapCollection(final Mappings.CollectionMapping mapping, final JsonArray jsonArray, final Converter<?> itemConverter) throws Exception {
         final Collection collection;
 
         if (SortedSet.class == mapping.raw) {
@@ -753,17 +763,16 @@ public class Mapper {
         }
 
         for (final JsonValue value : jsonArray) {
-            final Object element = toObject(value, mapping.arg);
-            collection.add(element);
+            collection.add(toObject(value, mapping.arg, itemConverter));
         }
         return collection;
     }
 
-    private Object buildArrayWithComponentType(final JsonArray jsonArray, final Class<?> componentType) throws Exception {
+    private Object buildArrayWithComponentType(final JsonArray jsonArray, final Class<?> componentType, final Converter<?> itemConverter) throws Exception {
         final Object array = Array.newInstance(componentType, jsonArray.size());
         int i = 0;
         for (final JsonValue value : jsonArray) {
-            Array.set(array, i++, toObject(value, componentType));
+            Array.set(array, i++, toObject(value, componentType, itemConverter));
         }
         return array;
     }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index 4f7e68c..cee2841 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -18,19 +18,6 @@
  */
 package org.apache.johnzon.mapper;
 
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.json.JsonReaderFactory;
-import javax.json.spi.JsonProvider;
-import javax.json.stream.JsonGenerator;
-import javax.json.stream.JsonGeneratorFactory;
-
 import org.apache.johnzon.mapper.access.AccessMode;
 import org.apache.johnzon.mapper.access.BaseAccessMode;
 import org.apache.johnzon.mapper.access.FieldAccessMode;
@@ -51,6 +38,19 @@ import org.apache.johnzon.mapper.converter.LongConverter;
 import org.apache.johnzon.mapper.converter.ShortConverter;
 import org.apache.johnzon.mapper.converter.StringConverter;
 
+import javax.json.JsonReaderFactory;
+import javax.json.spi.JsonProvider;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
 public class MapperBuilder {
     private static final Map<Class<?>, Converter<?>> DEFAULT_CONVERTERS = new HashMap<Class<?>, Converter<?>>();
 
@@ -94,7 +94,7 @@ public class MapperBuilder {
     protected boolean pretty;
     private AccessMode accessMode = new MethodAccessMode(false);
     private boolean treatByteArrayAsBase64;
-    private final Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>(DEFAULT_CONVERTERS);
+    private final Map<Type, Converter<?>> converters = new HashMap<Type, Converter<?>>(DEFAULT_CONVERTERS);
     private boolean supportConstructors;
     private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
 
@@ -230,11 +230,17 @@ public class MapperBuilder {
         return this;
     }
 
+    @Deprecated // use addConverter
     public MapperBuilder addPropertyEditor(final Class<?> clazz, final Converter<?> converter) {
         this.converters.put(clazz, converter);
         return this;
     }
 
+    public MapperBuilder addConverter(final Type clazz, final Converter<?> converter) {
+        this.converters.put(clazz, converter);
+        return this;
+    }
+
     public MapperBuilder setVersion(final int version) {
         this.version = version;
         return this;

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
index 0d8e94e..0c3a2da 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
@@ -18,6 +18,8 @@
  */
 package org.apache.johnzon.mapper.access;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -29,14 +31,77 @@ public class FieldAndMethodAccessMode extends BaseAccessMode {
     @Override
     public Map<String, Reader> doFindReaders(final Class<?> clazz) {
         final Map<String, Reader> readers = new HashMap<String, Reader>(fields.findReaders(clazz));
-        readers.putAll(methods.findReaders(clazz));
+        for (final Map.Entry<String, Reader> entry : methods.findReaders(clazz).entrySet()) {
+            final Reader existing = readers.get(entry.getKey());
+            if (existing == null) {
+                readers.put(entry.getKey(), entry.getValue());
+            } else {
+                readers.put(entry.getKey(), new CompositeReader(existing, entry.getValue()));
+            }
+        }
         return readers;
     }
 
     @Override
     public Map<String, Writer> doFindWriters(final Class<?> clazz) {
         final Map<String, Writer> writers = new HashMap<String, Writer>(fields.findWriters(clazz));
-        writers.putAll(methods.findWriters(clazz));
+        for (final Map.Entry<String, Writer> entry : methods.findWriters(clazz).entrySet()) {
+            final Writer existing = writers.get(entry.getKey());
+            if (existing == null) {
+                writers.put(entry.getKey(), entry.getValue());
+            } else {
+                writers.put(entry.getKey(), new CompositeWriter(existing, entry.getValue()));
+            }
+        }
         return writers;
     }
+
+    private static abstract class CompositeDecoratedType implements DecoratedType {
+        protected final DecoratedType type1;
+        private final DecoratedType type2;
+
+        private CompositeDecoratedType(final DecoratedType type1, final DecoratedType type2) {
+            this.type1 = type1;
+            this.type2 = type2;
+        }
+
+        @Override
+        public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+            final T found = type1.getAnnotation(clazz);
+            return found == null ? type2.getAnnotation(clazz) : found;
+        }
+
+        @Override
+        public Type getType() {
+            return type1.getType();
+        }
+    }
+
+    private static final class CompositeReader extends CompositeDecoratedType implements Reader {
+        private final Reader reader;
+
+        private CompositeReader(final Reader type1, final DecoratedType type2) {
+            super(type1, type2);
+            reader = type1;
+        }
+
+        @Override
+        public Object read(final Object instance) {
+            return reader.read(instance);
+        }
+    }
+
+    private static final class CompositeWriter extends CompositeDecoratedType implements Writer {
+        private final Writer writer;
+
+        private CompositeWriter(final Writer type1, final DecoratedType type2) {
+            super(type1, type2);
+            writer = type1;
+        }
+
+        @Override
+        public void write(final Object instance, final Object value) {
+            writer.write(instance, value);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
index 793356d..f5cd2ae 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
@@ -20,13 +20,17 @@ package org.apache.johnzon.mapper.converter;
 
 import org.apache.johnzon.mapper.Converter;
 
+import java.lang.reflect.Type;
 import java.util.HashMap;
 import java.util.Map;
 
-public class EnumConverter<T extends Enum<T>> implements Converter<T> {
+public class EnumConverter<T extends Enum<T>> implements Converter<T>, Converter.TypeAccess {
     private final Map<String, T> values;
+    private final Class<T> enumType;
 
     public EnumConverter(final Class<T> aClass) {
+        this.enumType = aClass;
+
         final T[] enumConstants = aClass.getEnumConstants();
         values = new HashMap<String, T>(enumConstants.length);
         for (final T t : enumConstants) {
@@ -43,4 +47,14 @@ public class EnumConverter<T extends Enum<T>> implements Converter<T> {
     public T fromString(final String text) {
         return values.get(text);
     }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + values.keySet();
+    }
+
+    @Override
+    public Type type() {
+        return enumType;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
index 60ccace..69cf6bb 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
@@ -25,6 +25,7 @@ import org.apache.johnzon.mapper.JohnzonVirtualObject;
 import org.apache.johnzon.mapper.JohnzonVirtualObjects;
 import org.apache.johnzon.mapper.access.AccessMode;
 import org.apache.johnzon.mapper.converter.DateWithCopyConverter;
+import org.apache.johnzon.mapper.converter.EnumConverter;
 
 import java.beans.ConstructorProperties;
 import java.lang.annotation.Annotation;
@@ -62,6 +63,7 @@ public class Mappings {
         public final boolean constructorHasArguments;
         public final String[] constructorParameters;
         public final Converter<?>[] constructorParameterConverters;
+        public final Converter<?>[] constructorItemParameterConverters;
         public final Type[] constructorParameterTypes;
 
         protected ClassMapping(final Class<?> clazz,
@@ -76,16 +78,25 @@ public class Mappings {
             if (this.constructorHasArguments) {
                 this.constructorParameterTypes = this.constructor.getGenericParameterTypes();
 
+                // TODO: java 8 gives access to it without annotation
                 this.constructorParameters = new String[this.constructor.getGenericParameterTypes().length];
                 final ConstructorProperties constructorProperties = this.constructor.getAnnotation(ConstructorProperties.class);
                 System.arraycopy(constructorProperties.value(), 0, this.constructorParameters, 0, this.constructorParameters.length);
 
                 this.constructorParameterConverters = new Converter<?>[this.constructor.getGenericParameterTypes().length];
+                this.constructorItemParameterConverters = new Converter<?>[this.constructorParameterConverters.length];
                 for (int i = 0; i < this.constructorParameters.length; i++) {
                     for (final Annotation a : this.constructor.getParameterAnnotations()[i]) {
                         if (a.annotationType() == JohnzonConverter.class) {
                             try {
-                                this.constructorParameterConverters[i] = JohnzonConverter.class.cast(a).value().newInstance();
+                                final Converter<?> converter = JohnzonConverter.class.cast(a).value().newInstance();
+                                if (matches(this.constructor.getParameterTypes()[i], converter)) {
+                                    this.constructorParameterConverters[i] = converter;
+                                    this.constructorItemParameterConverters[i] = null;
+                                } else {
+                                    this.constructorParameterConverters[i] = null;
+                                    this.constructorItemParameterConverters[i] = converter;
+                                }
                             } catch (final Exception e) {
                                 throw new IllegalArgumentException(e);
                             }
@@ -96,6 +107,7 @@ public class Mappings {
                 this.constructorParameterTypes = null;
                 this.constructorParameters = null;
                 this.constructorParameterConverters = null;
+                this.constructorItemParameterConverters = null;
             }
         }
 
@@ -142,6 +154,7 @@ public class Mappings {
         public final AccessMode.Reader reader;
         public final int version;
         public final Converter<Object> converter;
+        public final Converter<Object> itemConverter;
         public final boolean primitive;
         public final boolean array;
         public final boolean map;
@@ -150,14 +163,38 @@ public class Mappings {
         public Getter(final AccessMode.Reader reader,
                       final boolean primitive, final boolean array,
                       final boolean collection, final boolean map,
-                      final Converter<Object> converter, final int version) {
+                      final Converter<Object> converter,
+                      final int version) {
             this.reader = reader;
-            this.converter = converter;
             this.version = version;
             this.array = array;
             this.map = map && converter == null;
             this.collection = collection;
             this.primitive = primitive;
+            if (converter != null && matches(reader.getType(), converter)) {
+                this.converter = converter;
+                this.itemConverter = null;
+            } else if (converter != null) {
+                this.converter = null;
+                this.itemConverter = converter;
+            } else {
+                this.converter = null;
+                this.itemConverter = null;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Getter{" +
+                "reader=" + reader +
+                ", version=" + version +
+                ", converter=" + converter +
+                ", itemConverter=" + itemConverter +
+                ", primitive=" + primitive +
+                ", array=" + array +
+                ", map=" + map +
+                ", collection=" + collection +
+                '}';
         }
     }
 
@@ -166,26 +203,96 @@ public class Mappings {
         public final int version;
         public final Type paramType;
         public final Converter<?> converter;
+        public final Converter<?> itemConverter;
         public final boolean primitive;
         public final boolean array;
 
         public Setter(final AccessMode.Writer writer, final boolean primitive, final boolean array,
-                      final Type paramType, final Converter<?> converter, final int version) {
+                      final Type paramType, final Converter<?> converter,
+                      final int version) {
             this.writer = writer;
             this.paramType = paramType;
-            this.converter = converter;
             this.version = version;
             this.primitive = primitive;
             this.array = array;
+            if (converter != null && matches(writer.getType(), converter)) {
+                this.converter = converter;
+                this.itemConverter = null;
+            } else if (converter != null) {
+                this.converter = null;
+                this.itemConverter = converter;
+            } else {
+                this.converter = null;
+                this.itemConverter = null;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Setter{" +
+                "writer=" + writer +
+                ", version=" + version +
+                ", paramType=" + paramType +
+                ", converter=" + converter +
+                ", itemConverter=" + itemConverter +
+                ", primitive=" + primitive +
+                ", array=" + array +
+                '}';
         }
     }
 
+    // TODO: more ParameterizedType and maybe TypeClosure support
+    private static boolean matches(final Type type, final Converter<?> converter) {
+        Type convertType = null;
+        if (Converter.TypeAccess.class.isInstance(converter)) {
+            convertType = Converter.TypeAccess.class.cast(converter).type();
+        } else {
+            for (final Type pt : converter.getClass().getGenericInterfaces()) {
+                if (ParameterizedType.class.isInstance(pt) && ParameterizedType.class.cast(pt).getRawType() == Converter.class) {
+                    convertType = ParameterizedType.class.cast(pt).getActualTypeArguments()[0];
+                    break;
+                }
+            }
+        }
+
+        if (convertType == null) { // compatibility, previously nested converter were not supported
+            return true;
+        }
+
+        if (ParameterizedType.class.isInstance(type)) {
+            final ParameterizedType parameterizedType = ParameterizedType.class.cast(type);
+            final Type rawType = parameterizedType.getRawType();
+            if (Class.class.isInstance(rawType)) {
+                final Class<?> clazz = Class.class.cast(rawType);
+                if (Collection.class.isAssignableFrom(clazz) && parameterizedType.getActualTypeArguments().length == 1) {
+                    final Type argType = parameterizedType.getActualTypeArguments()[0];
+                    if (Class.class.isInstance(argType) && Class.class.isInstance(convertType)) {
+                        return !Class.class.cast(convertType).isAssignableFrom(Class.class.cast(argType));
+                    }
+                } else if (Map.class.isAssignableFrom(clazz) && parameterizedType.getActualTypeArguments().length == 2) {
+                    final Type argType = parameterizedType.getActualTypeArguments()[1];
+                    if (Class.class.isInstance(argType) && Class.class.isInstance(convertType)) {
+                        return !Class.class.cast(convertType).isAssignableFrom(Class.class.cast(argType));
+                    }
+                }
+                return true; // actually here we suppose we dont know
+            }
+        }
+        if (Class.class.isInstance(type)) {
+            final Class<?> clazz = Class.class.cast(type);
+            if (clazz.isArray()) {
+                return !Class.class.cast(convertType).isAssignableFrom(clazz.getComponentType());
+            }
+        }
+        return true;
+    }
+
     private static final JohnzonParameterizedType VIRTUAL_TYPE = new JohnzonParameterizedType(Map.class, String.class, Object.class);
 
     protected final ConcurrentMap<Type, ClassMapping> classes = new ConcurrentHashMap<Type, ClassMapping>();
     protected final ConcurrentMap<Type, CollectionMapping> collections = new ConcurrentHashMap<Type, CollectionMapping>();
     protected final Comparator<String> fieldOrdering;
-    protected final Map<Class<?>, Converter<?>> converters;
+    protected final ConcurrentMap<Type, Converter<?>> converters;
     private final boolean supportHiddenConstructors;
     private final boolean supportConstructors;
     private final AccessMode accessMode;
@@ -193,7 +300,7 @@ public class Mappings {
 
     public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode,
                     final boolean supportHiddenConstructors, final boolean supportConstructors,
-                    final int version, final Map<Class<?>, Converter<?>> converters) {
+                    final int version, final ConcurrentMap<Type, Converter<?>> converters) {
         this.fieldOrdering = attributeOrder;
         this.accessMode = accessMode;
         this.supportHiddenConstructors = supportHiddenConstructors;
@@ -446,14 +553,35 @@ public class Mappings {
     private Converter findConverter(final boolean copyDate, final AccessMode.DecoratedType method) {
         Converter converter = null;
         final JohnzonConverter annotation = method.getAnnotation(JohnzonConverter.class);
+
+
+        Type typeToTest = method.getType();
         if (annotation != null) {
             try {
                 converter = annotation.value().newInstance();
             } catch (final Exception e) {
                 throw new IllegalArgumentException(e);
             }
-        } else if (Class.class.isInstance(method.getType()) && Date.class.isAssignableFrom(Class.class.cast(method.getType())) && copyDate) {
-            converter = new DateWithCopyConverter(Converter.class.cast(converters.get(Date.class)));
+        } else if (ParameterizedType.class.isInstance(method.getType())) {
+            final ParameterizedType type = ParameterizedType.class.cast(method.getType());
+            final Type rawType = type.getRawType();
+            if (Class.class.isInstance(rawType)
+                    && Collection.class.isAssignableFrom(Class.class.cast(rawType))
+                    && type.getActualTypeArguments().length >= 1) {
+                typeToTest = type.getActualTypeArguments()[0];
+            } // TODO: map
+        }
+        if (converter == null && Class.class.isInstance(typeToTest)) {
+            final Class type = Class.class.cast(typeToTest);
+            if (Date.class.isAssignableFrom(type) && copyDate) {
+                converter = new DateWithCopyConverter(Converter.class.cast(converters.get(Date.class)));
+            } else if (type.isEnum()) {
+                converter = converters.get(type); // first ensure user didnt override it
+                if (converter == null) {
+                    converter = new EnumConverter(type);
+                    converters.put(type, converter);
+                }
+            }
         }
         return converter;
     }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java
new file mode 100644
index 0000000..841e4ec
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class EnumTest {
+
+    @Test
+    public void testSimpleEnumAccessModeBoth() {
+        testSimpleField(newTestMapperBuilder().setAccessModeName("both")
+            .build());
+    }
+
+    @Test
+    public void testSimpleEnumAccessModeField() {
+        testSimpleField(newTestMapperBuilder().setAccessModeName("field")
+            .build());
+    }
+
+    private void testSimpleField(Mapper mapper) {
+        SimpleObject object = new SimpleObject(MyEnum.TWO);
+
+        String objectAsString = mapper.writeObjectAsString(object);
+        Assert.assertEquals("{\"myEnum\":\"TWO\"}", objectAsString);
+
+        Assert.assertEquals(object.myEnum, mapper.<SimpleObject>readObject(objectAsString, SimpleObject.class).myEnum);
+    }
+
+    @Test
+    public void testSimpleEnumWithCollectionAccessModeBoth() {
+        testCollection(newTestMapperBuilder().setAccessModeName("both")
+            .build());
+    }
+
+    @Test
+    public void testSimpleEnumWithCollectionAccessModeField() {
+        testCollection(newTestMapperBuilder().setAccessModeName("field")
+            .build());
+    }
+
+
+    private void testCollection(Mapper mapper) {
+        CollectionObject object = new CollectionObject(MyEnum.ONE,
+            MyEnum.TWO,
+            MyEnum.THREE,
+            MyEnum.TWO);// duplicate entry isn't a mistake
+
+        String jsonString = "{\"enums\":[\"ONE\",\"TWO\",\"THREE\",\"TWO\"]}";
+
+        Assert.assertEquals(object.enums, mapper.<CollectionObject>readObject(jsonString, CollectionObject.class).enums);
+
+        String johnzonString = mapper.writeObjectAsString(object);
+        Assert.assertEquals(jsonString, johnzonString);
+        Assert.assertEquals(object.enums, mapper.<CollectionObject>readObject(johnzonString, CollectionObject.class).enums);
+    }
+
+
+    @Test
+    public void testAdvancedEnumAccessModeBoth() {
+        testAdvancedEnum(newTestMapperBuilder().setAccessModeName("both")
+            .build());
+    }
+
+    @Test
+    public void testAdvancedEnumAccessModeField() {
+        testAdvancedEnum(newTestMapperBuilder().setAccessModeName("field")
+            .build());
+    }
+
+    private void testAdvancedEnum(Mapper mapper) {
+        AdvancedEnumObject object = new AdvancedEnumObject(AdvancedEnum.VALUE_1, Arrays.asList(AdvancedEnum.VALUE_2,
+            AdvancedEnum.VALUE_1,
+            AdvancedEnum.VALUE_1,
+            AdvancedEnum.VALUE_2));
+
+        String jsonString = "{\"advancedEnum\":\"VALUE_1\",\"advancedEnums\":[\"VALUE_2\",\"VALUE_1\",\"VALUE_1\",\"VALUE_2\"]}";
+
+        AdvancedEnumObject johnzonObject = mapper.readObject(jsonString, AdvancedEnumObject.class);
+        Assert.assertEquals(object.advancedEnum, johnzonObject.advancedEnum);
+        Assert.assertEquals(object.advancedEnums, johnzonObject.advancedEnums);
+
+        String johnzonString = mapper.writeObjectAsString(object);
+        Assert.assertEquals(jsonString, johnzonString);
+
+        johnzonObject = mapper.readObject(johnzonString, AdvancedEnumObject.class);
+        Assert.assertEquals(object.advancedEnum, johnzonObject.advancedEnum);
+        Assert.assertEquals(object.advancedEnums, johnzonObject.advancedEnums);
+    }
+
+    private MapperBuilder newTestMapperBuilder() {
+        return new MapperBuilder().setAttributeOrder(new Comparator<String>() {
+            @Override
+            public int compare(final String o1, final String o2) {
+                return o1.compareTo(o2);
+            }
+        });
+    }
+
+    public enum MyEnum {
+        ONE,
+        TWO,
+        THREE
+    }
+
+    public static class SimpleObject {
+        private MyEnum myEnum;
+
+        private SimpleObject() {
+        }
+
+        private SimpleObject(MyEnum myEnum) {
+            this.myEnum = myEnum;
+        }
+    }
+
+    public static class CollectionObject {
+        private List<MyEnum> enums;
+
+        private CollectionObject() {
+        }
+
+        private CollectionObject(MyEnum... enums) {
+            this.enums = Arrays.asList(enums);
+        }
+    }
+
+
+    public enum AdvancedEnum {
+        VALUE_1("one", 1),
+        VALUE_2("two", 2);
+
+        private String string;
+        private int i;
+
+        private AdvancedEnum(String string, int i) {
+            this.string = string;
+            this.i = i;
+        }
+
+
+        public String getString() {
+            return string;
+        }
+
+        public int getI() {
+            return i;
+        }
+    }
+
+    public static class AdvancedEnumObject {
+        private AdvancedEnum advancedEnum;
+        private List<AdvancedEnum> advancedEnums;
+
+        private AdvancedEnumObject() {
+        }
+
+        public AdvancedEnumObject(AdvancedEnum advancedEnum, List<AdvancedEnum> advancedEnums) {
+            this.advancedEnum = advancedEnum;
+            this.advancedEnums = advancedEnums;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
new file mode 100644
index 0000000..f138a0b
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class ObjectConverterTest {
+
+    @Test
+    public void testObjectConverter() {
+        Contact contact = new Contact();
+        contact.linkedPersons.addAll(Arrays.asList(new Person("f1", "l1"), new Person("f2", "l2")));
+        contact.linkedPersonsArray = new Person[] { new Person("f3", "l3"), new Person("f4", "l4") };
+
+        MapperBuilder mapperBuilder = new MapperBuilder();
+        mapperBuilder.addConverter(Person.class, new PersonConverter());
+        Mapper mapper = mapperBuilder.setAccessModeName("both").setAttributeOrder(new Comparator<String>() {
+            @Override
+            public int compare(final String o1, final String o2) {
+                return o1.compareTo(o2);
+            }
+        }).build();
+
+        String s = mapper.writeObjectAsString(contact);
+        Contact c = mapper.readObject(s, Contact.class);
+        String expected = "{\"linkedPersons\":[\"f1|l1\",\"f2|l2\"],\"linkedPersonsArray\":[\"f3|l3\",\"f4|l4\"]}";
+        Assert.assertEquals(expected, s);
+        Assert.assertEquals(contact, c);
+    }
+
+
+    public static class PersonConverter implements Converter<Person> {
+        @Override
+        public String toString(Person instance) {
+            if (instance == null) {
+                return null;
+            }
+            return instance.getFirstName() + "|" + instance.getLastName();
+        }
+
+        @Override
+        public Person fromString(String text) {
+            if (text == null) {
+                return null;
+            }
+            String[] split = text.split("\\|");
+            if (split.length == 2) {
+                return new Person(split[0], split[1]);
+            }
+            return null;
+        }
+    }
+
+    public static class Contact {
+        @JohnzonConverter(PersonConverter.class)
+        private List<Person> linkedPersons = new ArrayList<Person>();
+
+        @JohnzonConverter(PersonConverter.class)
+        private Person[] linkedPersonsArray;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            final Contact contact = Contact.class.cast(o);
+            return linkedPersons.equals(contact.linkedPersons) && Arrays.equals(linkedPersonsArray, contact.linkedPersonsArray);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = linkedPersons.hashCode();
+            result = 31 * result + Arrays.hashCode(linkedPersonsArray);
+            return result;
+        }
+    }
+
+    public static class Person {
+        private String firstName;
+        private String lastName;
+
+        //no default constructor on purpose
+
+        private Person(String firstName, String lastName) {
+            this.firstName = firstName;
+            this.lastName = lastName;
+        }
+
+        public String getFirstName() {
+            return firstName;
+        }
+
+        public String getLastName() {
+            return lastName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof Person)) {
+                return false;
+            }
+
+            Person person = (Person) o;
+
+            if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) {
+                return false;
+            }
+            if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = firstName != null ? firstName.hashCode() : 0;
+            result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
+            return result;
+        }
+    }
+}
\ No newline at end of file