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 <[email protected]> Authored: Fri Jun 3 18:21:15 2016 +0200 Committer: Romain manni-Bucau <[email protected]> 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.
