Repository: johnzon Updated Branches: refs/heads/master b722755ae -> cc24e8e1c
JOHNZON-135 add MapperConfig switch to enable deduplication Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/cc24e8e1 Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/cc24e8e1 Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/cc24e8e1 Branch: refs/heads/master Commit: cc24e8e1cc63405d9493e0872c7149066a6e1880 Parents: b722755 Author: Mark Struberg <[email protected]> Authored: Sun Sep 24 23:22:23 2017 +0200 Committer: Mark Struberg <[email protected]> Committed: Sun Sep 24 23:22:23 2017 +0200 ---------------------------------------------------------------------- .../johnzon/jsonb/JohnzonIgnoreNestedTest.java | 4 +-- .../java/org/apache/johnzon/mapper/Mapper.java | 4 +-- .../apache/johnzon/mapper/MapperBuilder.java | 13 +++++++- .../org/apache/johnzon/mapper/MapperConfig.java | 11 ++++++- .../johnzon/mapper/MappingGeneratorImpl.java | 19 ++++++----- .../johnzon/mapper/MappingParserImpl.java | 33 ++++++++++++++------ .../johnzon/mapper/CircularObjectsTest.java | 4 +-- .../johnzon/mapper/JohnzonIgnoreNestedTest.java | 4 +-- .../apache/johnzon/mapper/MapperConfigTest.java | 2 +- .../java/org/superbiz/ExtendMappingTest.java | 2 +- 10 files changed, 67 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonIgnoreNestedTest.java ---------------------------------------------------------------------- diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonIgnoreNestedTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonIgnoreNestedTest.java index 5f1718d..2bf3815 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonIgnoreNestedTest.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonIgnoreNestedTest.java @@ -35,7 +35,7 @@ public class JohnzonIgnoreNestedTest { to.name = "to"; final Person from = new Person(); - from.name = "from"; + from.name = "myname"; from.street = "blastreet 1"; from.description = "gets ignored"; @@ -44,7 +44,7 @@ public class JohnzonIgnoreNestedTest { final Jsonb jsonb = JsonbProvider.provider().create().build(); - assertEquals("{\"name\":\"to\",\"person\":{\"name\":\"from\"},\"persons\":[\"/person\"]}", jsonb.toJson(to)); + assertEquals("{\"name\":\"to\",\"person\":{\"name\":\"myname\"},\"persons\":[{\"name\":\"myname\"}]}", jsonb.toJson(to)); } public static class To { http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/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 a369b2b..7d411c3 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 @@ -126,12 +126,12 @@ public class Mapper implements Closeable { } final JsonGenerator generator = generatorFactory.createGenerator(stream(stream)); - writeObject(object, generator, null, new JsonPointerTracker(null, "/")); + writeObject(object, generator, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); } public void writeObject(final Object object, final OutputStream stream) { final JsonGenerator generator = generatorFactory.createGenerator(stream(stream), config.getEncoding()); - writeObject(object, generator, null, new JsonPointerTracker(null, "/")); + writeObject(object, generator, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); } private void writeObject(final Object object, final JsonGenerator generator, final Collection<String> ignored, JsonPointerTracker jsonPointer) { http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/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 09457cb..d8998cd 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 @@ -133,6 +133,7 @@ public class MapperBuilder { private boolean failOnUnknownProperties; private SerializeValueFilter serializeValueFilter; private boolean useBigDecimalForFloats; + private boolean deduplicateObjects; public Mapper build() { if (readerFactory == null || generatorFactory == null) { @@ -223,7 +224,7 @@ public class MapperBuilder { skipNull, skipEmptyArray, treatByteArrayAsBase64, treatByteArrayAsBase64URL, readAttributeBeforeWrite, accessMode, encoding, attributeOrder, enforceQuoteString, failOnUnknownProperties, - serializeValueFilter, useBigDecimalForFloats), + serializeValueFilter, useBigDecimalForFloats, deduplicateObjects), closeables); } @@ -417,4 +418,14 @@ public class MapperBuilder { this.autoAdjustStringBuffers = autoAdjustStringBuffers; return this; } + + /** + * If any non-primitive Java Object gets serialised more than just one time, + * then we write a JsonPointer to the first occurrence instead. + * This will effectively also avoid endless loops in data with cycles! + */ + public MapperBuilder setDeduplicateObjects(boolean deduplicateObjects) { + this.deduplicateObjects = deduplicateObjects; + return this; + } } http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java index 5e9ccea..71cb51a 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java @@ -66,6 +66,7 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { private final boolean failOnUnknown; private final SerializeValueFilter serializeValueFilter; private final boolean useBigDecimalForFloats; + private final boolean deduplicateObjects; private final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriterCache; private final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaderCache; @@ -82,7 +83,9 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { final AccessMode accessMode, final Charset encoding, final Comparator<String> attributeOrder, final boolean enforceQuoteString, final boolean failOnUnknown, - final SerializeValueFilter serializeValueFilter, boolean useBigDecimalForFloats) { + final SerializeValueFilter serializeValueFilter, + final boolean useBigDecimalForFloats, + final boolean deduplicateObjects) { //CHECKSTYLE:ON this.objectConverterWriters = objectConverterWriters; this.objectConverterReaders = objectConverterReaders; @@ -104,8 +107,10 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { this.objectConverterWriterCache = new HashMap<Class<?>, ObjectConverter.Writer<?>>(objectConverterWriters.size()); this.objectConverterReaderCache = new HashMap<Class<?>, ObjectConverter.Reader<?>>(objectConverterReaders.size()); this.useBigDecimalForFloats = useBigDecimalForFloats; + this.deduplicateObjects = deduplicateObjects; } + public SerializeValueFilter getSerializeValueFilter() { return serializeValueFilter; } @@ -300,4 +305,8 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { public boolean isUseBigDecimalForFloats() { return useBigDecimalForFloats; } + + public boolean isDeduplicateObjects() { + return deduplicateObjects; + } } http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/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 6d73dfa..20b50ce 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 @@ -29,6 +29,7 @@ import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -36,13 +37,15 @@ public class MappingGeneratorImpl implements MappingGenerator { private final MapperConfig config; private final JsonGenerator generator; private final Mappings mappings; - private Map<Object, String> jsonPointers = new HashMap<>(); + private Map<Object, String> jsonPointers; MappingGeneratorImpl(MapperConfig config, JsonGenerator jsonGenerator, final Mappings mappings) { this.config = config; this.generator = jsonGenerator; this.mappings = mappings; + + this.jsonPointers = config.isDeduplicateObjects() ? new HashMap<>() : Collections.emptyMap(); } @Override @@ -57,7 +60,7 @@ public class MappingGeneratorImpl implements MappingGenerator { } else if (object instanceof JsonValue) { generator.write((JsonValue) object); } else { - doWriteObject(object, generator, false, null, new JsonPointerTracker(null, "/")); + doWriteObject(object, generator, false, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); } return this; } @@ -89,7 +92,7 @@ public class MappingGeneratorImpl implements MappingGenerator { } if (object instanceof Iterable) { - doWriteIterable((Iterable) object, ignoredProperties, new JsonPointerTracker(null, "/")); + doWriteIterable((Iterable) object, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); return; } @@ -291,7 +294,7 @@ public class MappingGeneratorImpl implements MappingGenerator { val, getter.objectConverter, getter.ignoreNested, - new JsonPointerTracker(jsonPointer, getterEntry.getKey())); + config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, getterEntry.getKey()) : null); } } @@ -341,7 +344,7 @@ public class MappingGeneratorImpl implements MappingGenerator { if (valJsonPointer != null) { writePrimitives(valJsonPointer); } else { - writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, new JsonPointerTracker(jsonPointer, i)); + writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null); } } generator.writeEnd(); @@ -354,7 +357,7 @@ public class MappingGeneratorImpl implements MappingGenerator { if (valJsonPointer != null) { writePrimitives(valJsonPointer); } else { - writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, new JsonPointerTracker(jsonPointer, i)); + writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null); } i++; } @@ -413,7 +416,7 @@ public class MappingGeneratorImpl implements MappingGenerator { if (t == null) { generator.writeNull(); } else { - writeItem(t, ignoredProperties, new JsonPointerTracker(jsonPointer, i)); + writeItem(t, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null); } } generator.writeEnd(); @@ -439,7 +442,7 @@ public class MappingGeneratorImpl implements MappingGenerator { if (t == null) { generator.writeNull(); } else { - writeItem(t, ignoredProperties, new JsonPointerTracker(jsonPointer, i)); + writeItem(t, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null); } } i++; http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/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 074cc02..101b438 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 @@ -45,6 +45,7 @@ import java.math.BigInteger; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.EnumMap; import java.util.EnumSet; @@ -95,7 +96,7 @@ public class MappingParserImpl implements MappingParser { * key: JsonPointer * value: already deserialised Object */ - private Map<String, Object> jsonPointers = new HashMap<>(); + private Map<String, Object> jsonPointers; public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader) { @@ -105,6 +106,12 @@ public class MappingParserImpl implements MappingParser { this.jsonReader = jsonReader; reverseAdaptersRegistry = new ConcurrentHashMap<>(config.getAdapters().size()); + + if (config.isDeduplicateObjects()) { + jsonPointers = new HashMap<>(); + } else { + jsonPointers = Collections.emptyMap(); + } } @@ -131,7 +138,7 @@ public class MappingParserImpl implements MappingParser { return (T) jsonValue; } if (JsonObject.class.isInstance(jsonValue)) { - return (T) buildObject(targetType, JsonObject.class.cast(jsonValue), applyObjectConverter, new JsonPointerTracker(null, "/")); + return (T) buildObject(targetType, JsonObject.class.cast(jsonValue), applyObjectConverter, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); } if (JsonString.class.isInstance(jsonValue) && (targetType == String.class || targetType == Object.class)) { return (T) JsonString.class.cast(jsonValue).getString(); @@ -160,7 +167,8 @@ public class MappingParserImpl implements MappingParser { if (Class.class.isInstance(targetType) && ((Class) targetType).isArray()) { final Class componentType = ((Class) targetType).getComponentType(); - return (T) buildArrayWithComponentType(jsonArray, componentType, config.findAdapter(componentType), new JsonPointerTracker(null, "/")); + return (T) buildArrayWithComponentType(jsonArray, componentType, config.findAdapter(componentType), + config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); } if (ParameterizedType.class.isInstance(targetType)) { @@ -171,10 +179,12 @@ public class MappingParserImpl implements MappingParser { } final Type arg = pt.getActualTypeArguments()[0]; - return (T) mapCollection(mapping, jsonArray, Class.class.isInstance(arg) ? config.findAdapter(Class.class.cast(arg)) : null, new JsonPointerTracker(null, "/")); + return (T) mapCollection(mapping, jsonArray, Class.class.isInstance(arg) ? config.findAdapter(Class.class.cast(arg)) : null, + config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null); } if (Object.class == targetType) { - return (T) new ArrayList(asList(Object[].class.cast(buildArrayWithComponentType(jsonArray, Object.class, null, new JsonPointerTracker(null, "/"))))); + return (T) new ArrayList(asList(Object[].class.cast(buildArrayWithComponentType(jsonArray, Object.class, null, + config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null)))); } } if (JsonValue.NULL.equals(jsonValue)) { @@ -295,7 +305,9 @@ public class MappingParserImpl implements MappingParser { t = classMapping.factory.create(createParameters(classMapping, object, jsonPointer)); } // store the new object under it's jsonPointer in case it gets referenced later - jsonPointers.put(jsonPointer.toString(), t); + if (config.isDeduplicateObjects()) { + jsonPointers.put(jsonPointer.toString(), t); + } for (final Map.Entry<String, Mappings.Setter> setter : classMapping.setters.entrySet()) { final JsonValue jsonValue = object.get(setter.getKey()); @@ -335,7 +347,8 @@ public class MappingParserImpl implements MappingParser { 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, new JsonPointerTracker(jsonPointer, entry.getKey()))); + classMapping.anySetter.invoke(t, key, toValue(null, entry.getValue(), null, null, Object.class, null, + config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, entry.getKey()) : null)); } catch (final IllegalAccessException e) { throw new IllegalStateException(e); } catch (final InvocationTargetException e) { @@ -616,7 +629,8 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { - collection.add(JsonValue.NULL.equals(value) ? null : toObject(null, value, mapping.arg, itemConverter, new JsonPointerTracker(jsonPointer, i))); + collection.add(JsonValue.NULL.equals(value) ? null : toObject(null, value, mapping.arg, itemConverter, + config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null)); i++; } @@ -647,7 +661,8 @@ public class MappingParserImpl implements MappingParser { mapping.factory.getParameterConverter()[i], mapping.factory.getParameterItemConverter()[i], mapping.factory.getParameterTypes()[i], - null, new JsonPointerTracker(jsonPointer, paramName)); //X TODO ObjectConverter in @JOhnzonConverter with Constructors! + null, + config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, paramName) : null); //X TODO ObjectConverter in @JOhnzonConverter with Constructors! } return objects; http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java index af0414a..4af1d29 100644 --- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java @@ -41,7 +41,7 @@ public class CircularObjectsTest { john.setMarriedTo(marry); marry.setMarriedTo(john); - Mapper mapper = new MapperBuilder().setAccessModeName("field").build(); + Mapper mapper = new MapperBuilder().setAccessModeName("field").setDeduplicateObjects(true).build(); String ser = mapper.writeObjectAsString(john); assertNotNull(ser); @@ -82,7 +82,7 @@ public class CircularObjectsTest { sue.setFather(karl); sue.setMother(andrea); - Mapper mapper = new MapperBuilder().setAccessModeName("field").build(); + Mapper mapper = new MapperBuilder().setAccessModeName("field").setDeduplicateObjects(true).build(); { // test karl http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonIgnoreNestedTest.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonIgnoreNestedTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonIgnoreNestedTest.java index 1c914d0..81d5f83 100644 --- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonIgnoreNestedTest.java +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonIgnoreNestedTest.java @@ -33,7 +33,7 @@ public class JohnzonIgnoreNestedTest { to.name = "to"; final Person from = new Person(); - from.name = "from"; + from.name = "myname"; from.street = "blastreet 1"; from.description = "gets ignored"; @@ -41,7 +41,7 @@ public class JohnzonIgnoreNestedTest { to.persons = singletonList(from); final Mapper mapper = new MapperBuilder().setAttributeOrder(Comparator.naturalOrder()).build(); - assertEquals("{\"name\":\"to\",\"person\":{\"name\":\"from\"},\"persons\":[\"/person\"]}", mapper.writeObjectAsString(to)); + assertEquals("{\"name\":\"to\",\"person\":{\"name\":\"myname\"},\"persons\":[{\"name\":\"myname\"}]}", mapper.writeObjectAsString(to)); } public static class To { http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java index a84a1cd..0a9cc7f 100644 --- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java @@ -166,7 +166,7 @@ public class MapperConfigTest { new FieldAccessMode(true, true), Charset.forName("UTF-8"), null, - false, false, null, false); + false, false, null, false, false); } http://git-wip-us.apache.org/repos/asf/johnzon/blob/cc24e8e1/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java b/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java index 6c0c3de..142ca18 100644 --- a/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java +++ b/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java @@ -71,7 +71,7 @@ public class ExtendMappingTest { public int compare(final String o1, final String o2) { return o1.compareTo(o2); } - }, false, false, null, false)); + }, false, false, null, false, false)); } @Override
