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 2016/06/03 16:21:29 UTC

incubator-johnzon git commit: JOHNZON-85 @JohnzonAny

Repository: incubator-johnzon
Updated Branches:
  refs/heads/master 36816c64b -> 06cedc17a


JOHNZON-85 @JohnzonAny


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

Branch: refs/heads/master
Commit: 06cedc17a23b31f08c227a9deb46d15256f4d449
Parents: 36816c6
Author: Romain manni-Bucau <rm...@gmail.com>
Authored: Fri Jun 3 18:21:15 2016 +0200
Committer: Romain manni-Bucau <rm...@gmail.com>
Committed: Fri Jun 3 18:21:15 2016 +0200

----------------------------------------------------------------------
 .../apache/johnzon/jsonb/JsonbAccessMode.java   | 26 +++++-
 .../org/apache/johnzon/mapper/JohnzonAny.java   | 30 +++++++
 .../johnzon/mapper/MappingGeneratorImpl.java    |  9 ++
 .../johnzon/mapper/MappingParserImpl.java       | 14 +++
 .../org/apache/johnzon/mapper/Mappings.java     | 91 +++++++++++---------
 .../johnzon/mapper/access/AccessMode.java       |  3 +
 .../johnzon/mapper/access/BaseAccessMode.java   | 38 ++++++++
 .../johnzon/mapper/access/FieldAccessMode.java  |  3 +-
 .../johnzon/mapper/access/MethodAccessMode.java |  3 +-
 .../johnzon/mapper/JohnzonAnyMappingTest.java   | 81 +++++++++++++++++
 src/site/markdown/index.md                      | 29 +++++++
 11 files changed, 285 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index 5f30a62..d41a98d 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -29,9 +29,11 @@ import org.apache.johnzon.jsonb.serializer.JohnzonDeserializationContext;
 import org.apache.johnzon.jsonb.serializer.JohnzonSerializationContext;
 import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
 import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.JohnzonAny;
 import org.apache.johnzon.mapper.ObjectConverter;
 import org.apache.johnzon.mapper.TypeAwareAdapter;
 import org.apache.johnzon.mapper.access.AccessMode;
+import org.apache.johnzon.mapper.access.BaseAccessMode;
 import org.apache.johnzon.mapper.access.FieldAccessMode;
 import org.apache.johnzon.mapper.access.FieldAndMethodAccessMode;
 import org.apache.johnzon.mapper.access.MethodAccessMode;
@@ -104,6 +106,17 @@ public class JsonbAccessMode implements AccessMode, Closeable {
     private final Collection<JohnzonAdapterFactory.Instance<?>> toRelease = new ArrayList<>();
     private final Supplier<JsonParserFactory> parserFactory;
     private final ConcurrentMap<Class<?>, ParsingCacheEntry> parsingCache = new ConcurrentHashMap<>();
+    private final BaseAccessMode partialDelegate = new BaseAccessMode(false, false) {
+        @Override
+        protected Map<String, Reader> doFindReaders(Class<?> clazz) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected Map<String, Writer> doFindWriters(Class<?> clazz) {
+            throw new UnsupportedOperationException();
+        }
+    };
 
     public JsonbAccessMode(final PropertyNamingStrategy propertyNamingStrategy, final String orderValue,
                            final PropertyVisibilityStrategy visibilityStrategy, final boolean caseSensitive,
@@ -329,7 +342,7 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         final Map<String, Reader> result = keyComparator == null ? new HashMap<>() : new TreeMap<>(keyComparator);
         for (final Map.Entry<String, Reader> entry : readers.entrySet()) {
             final Reader initialReader = entry.getValue();
-            if (isTransient(initialReader, visibility)) {
+            if (isTransient(initialReader, visibility) || initialReader.getAnnotation(JohnzonAny.class) != null) {
                 continue;
             }
 
@@ -525,8 +538,19 @@ public class JsonbAccessMode implements AccessMode, Closeable {
     }
 
     @Override
+    public Method findAnyGetter(final Class<?> clazz) {
+        return partialDelegate.findAnyGetter(clazz);
+    }
+
+    @Override
+    public Method findAnySetter(final Class<?> clazz) {
+        return partialDelegate.findAnySetter(clazz);
+    }
+
+    @Override
     public void afterParsed(final Class<?> clazz) {
         parsingCache.remove(clazz);
+        partialDelegate.afterParsed(clazz);
     }
 
     private boolean isReversedAdapter(final Class<?> payloadType, final Class<?> aClass, final Adapter<?, ?> instance) {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
new file mode 100644
index 0000000..b134de7
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
@@ -0,0 +1,30 @@
+/*
+ * 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 java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface JohnzonAny {
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
index a88ab83..38b5315 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
@@ -270,6 +270,15 @@ public class MappingGeneratorImpl implements MappingGenerator {
                     getterEntry.getKey(),
                     val, getter.objectConverter);
         }
+
+        // @JohnzonAny doesn't respect comparator since it is a map and not purely in the model we append it after and
+        // sorting is up to the user for this part (TreeMap if desired)
+        if (classMapping.anyGetter != null) {
+            final Map<String, Object> any = Map.class.cast(classMapping.anyGetter.reader.read(object));
+            if (any != null) {
+                writeMapBody(any, null);
+            }
+        }
     }
 
     private void writeValue(final Class<?> type,

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index b5a7fab..7185bb1 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -317,6 +317,20 @@ public class MappingParserImpl implements MappingParser {
                 }
             }
         }
+        if (classMapping.anySetter != null) {
+            for (final Map.Entry<String, JsonValue> entry : object.entrySet()) {
+                final String key = entry.getKey();
+                if (!classMapping.setters.containsKey(key)) {
+                    try {
+                        classMapping.anySetter.invoke(t, key, toValue(null, entry.getValue(), null, null, Object.class, null));
+                    } catch (final IllegalAccessException e) {
+                        throw new IllegalStateException(e);
+                    } catch (final InvocationTargetException e) {
+                        throw new MapperException(e.getCause());
+                    }
+                }
+            }
+        }
 
         return t;
     }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index 45a5690..7dd016c 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -19,6 +19,7 @@
 package org.apache.johnzon.mapper;
 
 import org.apache.johnzon.mapper.access.AccessMode;
+import org.apache.johnzon.mapper.access.MethodAccessMode;
 import org.apache.johnzon.mapper.converter.DateWithCopyConverter;
 import org.apache.johnzon.mapper.converter.EnumConverter;
 import org.apache.johnzon.mapper.internal.AdapterKey;
@@ -27,6 +28,7 @@ import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
+import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
@@ -60,11 +62,14 @@ public class Mappings {
         public final Adapter adapter;
         public final ObjectConverter.Reader reader;
         public final ObjectConverter.Writer writer;
+        public final Getter anyGetter;
+        public final Method anySetter;
 
         protected ClassMapping(final Class<?> clazz, final AccessMode.Factory factory,
                                final Map<String, Getter> getters, final Map<String, Setter> setters,
                                final Adapter<?, ?> adapter,
-                               final ObjectConverter.Reader<?> reader, final ObjectConverter.Writer<?> writer) {
+                               final ObjectConverter.Reader<?> reader, final ObjectConverter.Writer<?> writer,
+                               final Getter anyGetter, final Method anySetter) {
             this.clazz = clazz;
             this.factory = factory;
             this.getters = getters;
@@ -72,6 +77,8 @@ public class Mappings {
             this.adapter = adapter;
             this.writer = writer;
             this.reader = reader;
+            this.anyGetter = anyGetter;
+            this.anySetter = anySetter;
         }
     }
 
@@ -145,15 +152,15 @@ public class Mappings {
         @Override
         public String toString() {
             return "Getter{" +
-                "reader=" + reader +
-                ", version=" + version +
-                ", converter=" + converter +
-                ", itemConverter=" + itemConverter +
-                ", primitive=" + primitive +
-                ", array=" + array +
-                ", map=" + map +
-                ", collection=" + collection +
-                '}';
+                    "reader=" + reader +
+                    ", version=" + version +
+                    ", converter=" + converter +
+                    ", itemConverter=" + itemConverter +
+                    ", primitive=" + primitive +
+                    ", array=" + array +
+                    ", map=" + map +
+                    ", collection=" + collection +
+                    '}';
         }
     }
 
@@ -185,7 +192,7 @@ public class Mappings {
                 if (converter instanceof ObjectConverter.Reader) {
                     theObjectConverter = (ObjectConverter.Reader) converter;
                 }
-                if (theObjectConverter == null){
+                if (theObjectConverter == null) {
                     Adapter adapter;
                     if (converter instanceof Converter) {
                         adapter = new ConverterAdapter((Converter) converter);
@@ -209,14 +216,14 @@ public class Mappings {
         @Override
         public String toString() {
             return "Setter{" +
-                "writer=" + writer +
-                ", version=" + version +
-                ", paramType=" + paramType +
-                ", converter=" + converter +
-                ", itemConverter=" + itemConverter +
-                ", primitive=" + primitive +
-                ", array=" + array +
-                '}';
+                    "writer=" + writer +
+                    ", version=" + version +
+                    ", paramType=" + paramType +
+                    ", converter=" + converter +
+                    ", itemConverter=" + itemConverter +
+                    ", primitive=" + primitive +
+                    ", array=" + array +
+                    '}';
         }
     }
 
@@ -284,11 +291,11 @@ public class Mappings {
         } else if (type == long.class || type == Long.class) {
             return true;
         } else if (type == int.class || type == Integer.class
-            || type == byte.class || type == Byte.class
-            || type == short.class || type == Short.class) {
+                || type == byte.class || type == Byte.class
+                || type == short.class || type == Short.class) {
             return true;
         } else if (type == double.class || type == Double.class
-            || type == float.class || type == Float.class) {
+                || type == float.class || type == Float.class) {
             return true;
         } else if (type == boolean.class || type == Boolean.class) {
             return true;
@@ -372,10 +379,16 @@ public class Mappings {
             addSetterIfNeeded(setters, key, writer.getValue(), copyDate);
         }
 
+        final Method anyGetter = accessMode.findAnyGetter(clazz);
         final ClassMapping mapping = new ClassMapping(
                 clazz, accessMode.findFactory(clazz), getters, setters,
                 accessMode.findAdapter(clazz),
-                accessMode.findReader(clazz), accessMode.findWriter(clazz));
+                accessMode.findReader(clazz),
+                accessMode.findWriter(clazz),
+                anyGetter != null ? new Getter(
+                        new MethodAccessMode.MethodReader(anyGetter, anyGetter.getReturnType()),
+                        false, false, false, true, null, null, -1) : null,
+                accessMode.findAnySetter(clazz));
 
         accessMode.afterParsed(clazz);
 
@@ -386,8 +399,8 @@ public class Mappings {
         Class<?> clazz = inClazz;
         // unproxy to get a clean model
         while (clazz != null && clazz != Object.class
-            && (clazz.getName().contains("$$") || clazz.getName().contains("$proxy")
-            || clazz.getName().startsWith("org.apache.openjpa.enhance.") /* subclassing mode, not the default */)) {
+                && (clazz.getName().contains("$$") || clazz.getName().contains("$proxy")
+                || clazz.getName().startsWith("org.apache.openjpa.enhance.") /* subclassing mode, not the default */)) {
             clazz = clazz.getSuperclass();
         }
         if (clazz == null || clazz == Object.class) { // shouldn't occur but a NPE protection
@@ -412,9 +425,9 @@ public class Mappings {
             final Type param = value.getType();
             final Class<?> returnType = Class.class.isInstance(param) ? Class.class.cast(param) : null;
             final Setter setter = new Setter(
-                value, isPrimitive(param), returnType != null && returnType.isArray(), param,
-                findConverter(copyDate, value), value.findObjectConverterReader(),
-                writeIgnore != null ? writeIgnore.minVersion() : -1);
+                    value, isPrimitive(param), returnType != null && returnType.isArray(), param,
+                    findConverter(copyDate, value), value.findObjectConverterReader(),
+                    writeIgnore != null ? writeIgnore.minVersion() : -1);
             setters.put(key, setter);
         }
     }
@@ -428,13 +441,13 @@ public class Mappings {
             final Class<?> returnType = Class.class.isInstance(value.getType()) ? Class.class.cast(value.getType()) : null;
             final ParameterizedType pt = ParameterizedType.class.isInstance(value.getType()) ? ParameterizedType.class.cast(value.getType()) : null;
             final Getter getter = new Getter(value, isPrimitive(returnType),
-                returnType != null && returnType.isArray(),
-                (pt != null && Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
-                    || (returnType != null && Collection.class.isAssignableFrom(returnType)),
-                (pt != null && Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
-                    || (returnType != null && Map.class.isAssignableFrom(returnType)),
-                findConverter(copyDate, value), value.findObjectConverterWriter(),
-                readIgnore != null ? readIgnore.minVersion() : -1);
+                    returnType != null && returnType.isArray(),
+                    (pt != null && Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
+                            || (returnType != null && Collection.class.isAssignableFrom(returnType)),
+                    (pt != null && Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
+                            || (returnType != null && Map.class.isAssignableFrom(returnType)),
+                    findConverter(copyDate, value), value.findObjectConverterWriter(),
+                    readIgnore != null ? readIgnore.minVersion() : -1);
             getters.put(key, getter);
         }
     }
@@ -507,8 +520,8 @@ public class Mappings {
             final ParameterizedType type = ParameterizedType.class.cast(decoratedType.getType());
             final Type rawType = type.getRawType();
             if (Class.class.isInstance(rawType)
-                && Collection.class.isAssignableFrom(Class.class.cast(rawType))
-                && type.getActualTypeArguments().length >= 1) {
+                    && Collection.class.isAssignableFrom(Class.class.cast(rawType))
+                    && type.getActualTypeArguments().length >= 1) {
                 typeToTest = type.getActualTypeArguments()[0];
             } // TODO: map
         }
@@ -533,7 +546,7 @@ public class Mappings {
                     if (adapterEntry.getKey().getFrom() == type && !(
                             // ignore internal converters to let primitives be correctly handled
                             ConverterAdapter.class.isInstance(adapterEntry.getValue()) &&
-                            ConverterAdapter.class.cast(adapterEntry.getValue()).getConverter().getClass().getName().startsWith("org.apache.johnzon.mapper."))) {
+                                    ConverterAdapter.class.cast(adapterEntry.getValue()).getConverter().getClass().getName().startsWith("org.apache.johnzon.mapper."))) {
 
                         if (converter != null) {
                             throw new IllegalArgumentException("Ambiguous adapter for " + decoratedType);
@@ -652,7 +665,7 @@ public class Mappings {
                 final String key = setter.getKey();
                 final Object rawValue = nested.get(key);
                 Object val = value == null || setterValue.converter == null ?
-                    rawValue : Converter.class.cast(setterValue.converter).toString(rawValue);
+                        rawValue : Converter.class.cast(setterValue.converter).toString(rawValue);
                 if (val == null) {
                     continue;
                 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
index bf4eff1..8575aa3 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
@@ -22,6 +22,7 @@ import org.apache.johnzon.mapper.Adapter;
 import org.apache.johnzon.mapper.ObjectConverter;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.util.Comparator;
 import java.util.Map;
@@ -60,6 +61,8 @@ public interface AccessMode {
     ObjectConverter.Reader<?> findReader(Class<?> clazz);
     ObjectConverter.Writer<?> findWriter(Class<?> clazz);
     Adapter<?, ?> findAdapter(Class<?> clazz);
+    Method findAnyGetter(Class<?> clazz);
+    Method findAnySetter(Class<?> clazz);
 
     /**
      * Called once johnzon will not use AccessMode anymore. Can be used to clean up any local cache.

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
index 0da7531..e1bba2e 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
@@ -19,6 +19,7 @@
 package org.apache.johnzon.mapper.access;
 
 import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.JohnzonAny;
 import org.apache.johnzon.mapper.Converter;
 import org.apache.johnzon.mapper.JohnzonConverter;
 import org.apache.johnzon.mapper.MapperConverter;
@@ -31,6 +32,7 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.GenericDeclaration;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
@@ -214,6 +216,42 @@ public abstract class BaseAccessMode implements AccessMode {
         };
     }
 
+    @Override
+    public Method findAnyGetter(final Class<?> clazz) {
+        Method m = null;
+        for (final Method current : clazz.getMethods()) {
+            if (current.getAnnotation(JohnzonAny.class) != null) {
+                if (current.getParameterTypes().length == 0) {
+                    if (!Map.class.isAssignableFrom(current.getReturnType())) {
+                        throw new IllegalArgumentException("@JohnzonAny getters can only return a Map<String, Object>");
+                    }
+                    if (m != null) {
+                        throw new IllegalArgumentException("Ambiguous @JohnzonAny on " + m + " and " + current);
+                    }
+                    m = current;
+                }
+            }
+        }
+        return m;
+    }
+
+    @Override
+    public Method findAnySetter(final Class<?> clazz) {
+        Method m = null;
+        for (final Method current : clazz.getMethods()) {
+            if (current.getAnnotation(JohnzonAny.class) != null) {
+                final Class<?>[] parameterTypes = current.getParameterTypes();
+                if (parameterTypes.length == 2 && parameterTypes[0] == String.class && parameterTypes[1] == Object.class) {
+                    if (m != null) {
+                        throw new IllegalArgumentException("Ambiguous @JohnzonAny on " + m + " and " + current);
+                    }
+                    m = current;
+                }
+            }
+        }
+        return m;
+    }
+
     private <T> Map<String, T> sanitize(final Class<?> type, final Map<String, T> delegate) {
         for (final Map.Entry<Class<?>, String[]> entry : fieldsToRemove.entrySet()) {
             if (entry.getKey().isAssignableFrom(type)) {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
index 0bb6a4f..81e0b5d 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
@@ -19,6 +19,7 @@
 package org.apache.johnzon.mapper.access;
 
 import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.JohnzonAny;
 import org.apache.johnzon.mapper.JohnzonProperty;
 import org.apache.johnzon.mapper.MapperException;
 import org.apache.johnzon.mapper.ObjectConverter;
@@ -40,7 +41,7 @@ public class FieldAccessMode extends BaseAccessMode {
         final Map<String, Reader> readers = new HashMap<String, Reader>();
         for (final Map.Entry<String, Field> f : fields(clazz).entrySet()) {
             final String key = f.getKey();
-            if (isIgnored(key)) {
+            if (isIgnored(key) || f.getValue().getAnnotation(JohnzonAny.class) != null) {
                 continue;
             }
 

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
index 4309c28..524123b 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
@@ -19,6 +19,7 @@
 package org.apache.johnzon.mapper.access;
 
 import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.JohnzonAny;
 import org.apache.johnzon.mapper.JohnzonProperty;
 import org.apache.johnzon.mapper.MapperException;
 import org.apache.johnzon.mapper.ObjectConverter;
@@ -48,7 +49,7 @@ public class MethodAccessMode extends BaseAccessMode {
         for (final PropertyDescriptor descriptor : propertyDescriptors) {
             final Method readMethod = descriptor.getReadMethod();
             if (readMethod != null && readMethod.getDeclaringClass() != Object.class) {
-                if (isIgnored(descriptor.getName())) {
+                if (isIgnored(descriptor.getName()) || readMethod.getAnnotation(JohnzonAny.class) != null) {
                     continue;
                 }
                 readers.put(extractKey(descriptor), new MethodReader(readMethod, fixType(clazz, readMethod.getGenericReturnType())));

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java
new file mode 100644
index 0000000..cb52d74
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.Test;
+
+import java.io.StringReader;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class JohnzonAnyMappingTest {
+    @Test
+    public void roundTrip() {
+        final Mapper mapper = new MapperBuilder().setAttributeOrder(new Comparator<String>() {
+            @Override
+            public int compare(final String o1, final String o2) {
+                return o1.compareTo(o2);
+            }
+        }).build();
+
+        final AnyMe instance = new AnyMe();
+        instance.name = "test";
+        instance.any.putAll(new HashMap<String, Object>() {{
+            put("a", "n");
+            put("y", ".");
+        }});
+        // sorting is expected to be fields then any with the Map ordering
+        assertEquals("{\"name\":\"test\",\"a\":\"n\",\"y\":\".\"}", mapper.writeObjectAsString(instance));
+
+        final AnyMe loaded = mapper.readObject(new StringReader("{\"name\":\"test\",\"z\":2016, \"a\":\"n\",\"y\":\".\"}"), AnyMe.class);
+        assertEquals("test", loaded.name);
+        assertEquals(new HashMap<String, Object>() {{
+            put("a", "n");
+            put("y", ".");
+            put("z", 2016);
+        }}, loaded.any);
+    }
+
+    public static class AnyMe {
+        private String name;
+        private Map<String, Object> any = new TreeMap<String, Object>();
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        @JohnzonAny
+        public Map<String, Object> getAny() {
+            return any;
+        }
+
+        @JohnzonAny
+        public void handle(final String key, final Object val) {
+            any.put(key, val);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/06cedc17/src/site/markdown/index.md
----------------------------------------------------------------------
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 528054f..8c97a99 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -183,6 +183,35 @@ public class MyModel {
 }
 ]]></pre>
 
+#### @JohnzonAny
+
+If you don't fully know you model but want to handle all keys you can use @JohnzonAny to capture/serialize them all:
+
+<pre class="prettyprint linenums"><![CDATA[
+public class AnyMe {
+    private String name; // known
+    private Map<String, Object> any = new TreeMap<String, Object>(); // unknown
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Any
+    public Map<String, Object> getAny() {
+        return any;
+    }
+
+    @Any
+    public void handle(final String key, final Object val) {
+        any.put(key, val);
+    }
+}
+]]></pre>
+
 #### AccessMode
 
 On MapperBuilder you have several AccessMode available by default but you can also create your own one.