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.