This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
commit 4bdb367a6b8c3ab9bbb23498b836d091c5598a72 Author: Romain Manni-Bucau <[email protected]> AuthorDate: Thu Apr 4 11:15:37 2019 +0200 JOHNZON-207 some enhancement on Xavier's patch --- .../main/java/org/apache/johnzon/core/Types.java | 69 ++++++-- .../java/org/apache/johnzon/core/TypesTest.java | 6 +- .../org/apache/johnzon/jsonb/JohnzonBuilder.java | 5 +- .../org/apache/johnzon/jsonb/JsonbAccessMode.java | 195 +++++++++++++-------- ...MoreTests.java => SeriaizersRoundTripTest.java} | 116 +++++++++--- .../johnzon/mapper/DynamicMappingGenerator.java | 25 +-- .../johnzon/mapper/MappingGeneratorImpl.java | 17 +- .../apache/johnzon/mapper/MappingParserImpl.java | 11 +- 8 files changed, 316 insertions(+), 128 deletions(-) diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/Types.java b/johnzon-core/src/main/java/org/apache/johnzon/core/Types.java index 3e00e1d..d06d27f 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/Types.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/Types.java @@ -41,11 +41,26 @@ public class Types { * For the last example (UUIDStringConverter), nowhere in its hierarchy is a type directly implementing * Converter[UUID, String] but this method is capable of reconstructing that information. */ - public static ParameterizedType findParameterizedType(Class<?> klass, Class<?> parameterizedClass) { + public ParameterizedType findParameterizedType(Class<?> klass, Class<?> parameterizedClass) { return new ParameterizedTypeImpl(parameterizedClass, resolveArgumentTypes(klass, parameterizedClass)); } - private static Type[] resolveArgumentTypes(Type type, Class<?> parameterizedClass) { + public Class<?> findParamType(final ParameterizedType type, final Class<?> expectedWrapper) { + if (type.getActualTypeArguments().length != 1) { + return null; + } + final Class<?> asClass = asClass(type.getRawType()); + if (asClass == null || !expectedWrapper.isAssignableFrom(asClass)) { + return null; + } + return asClass(type.getActualTypeArguments()[0]); + } + + public Class<?> asClass(final Type type) { + return Class.class.isInstance(type) ? Class.class.cast(type) : null; + } + + private Type[] resolveArgumentTypes(Type type, Class<?> parameterizedClass) { if (type instanceof Class<?>) { return resolveArgumentTypes((Class<?>) type, parameterizedClass); } @@ -55,7 +70,7 @@ public class Types { throw new IllegalArgumentException("Cannot resolve argument types from " + type.getClass().getSimpleName()); } - private static Type[] resolveArgumentTypes(Class<?> type, Class<?> parameterizedClass) { + private Type[] resolveArgumentTypes(Class<?> type, Class<?> parameterizedClass) { if (parameterizedClass.equals(type)) { // May return Class[] instead of Type[], so copy it as a Type[] to avoid // problems in visit(ParameterizedType) @@ -74,7 +89,7 @@ public class Types { throw new IllegalArgumentException(String.format("%s is not assignable from %s", type, parameterizedClass)); } - private static Type[] resolveArgumentTypes(ParameterizedType type, Class<?> parameterizedClass) { + private Type[] resolveArgumentTypes(ParameterizedType type, Class<?> parameterizedClass) { Class<?> rawType = (Class<?>) type.getRawType(); // always a Class TypeVariable<?>[] typeVariables = rawType.getTypeParameters(); Type[] types = resolveArgumentTypes(rawType, parameterizedClass); @@ -91,16 +106,12 @@ public class Types { return types; } - private Types() { - // no-op - } - private static class ParameterizedTypeImpl implements ParameterizedType { private final Type rawType; private final Type[] arguments; - ParameterizedTypeImpl(Type rawType, Type... arguments) { + private ParameterizedTypeImpl(final Type rawType, final Type... arguments) { this.rawType = rawType; this.arguments = arguments; } @@ -120,6 +131,44 @@ public class Types { return arguments; } - // TODO equals, hashcode, toString if needed + @Override + public int hashCode() { + return Arrays.hashCode(arguments) ^ (rawType == null ? 0 : rawType.hashCode()); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof ParameterizedType) { + final ParameterizedType that = (ParameterizedType) obj; + final Type thatRawType = that.getRawType(); + return that.getOwnerType() == null + && (rawType == null ? thatRawType == null : rawType.equals(thatRawType)) + && Arrays.equals(arguments, that.getActualTypeArguments()); + } else { + return false; + } + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + buffer.append(((Class<?>) rawType).getSimpleName()); + final Type[] actualTypes = getActualTypeArguments(); + if (actualTypes.length > 0) { + buffer.append("<"); + int length = actualTypes.length; + for (int i = 0; i < length; i++) { + buffer.append(actualTypes[i].toString()); + if (i != actualTypes.length - 1) { + buffer.append(","); + } + } + + buffer.append(">"); + } + return buffer.toString(); + } } } diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/TypesTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/TypesTest.java index d7ff83d..73baa18 100644 --- a/johnzon-core/src/test/java/org/apache/johnzon/core/TypesTest.java +++ b/johnzon-core/src/test/java/org/apache/johnzon/core/TypesTest.java @@ -45,8 +45,10 @@ public class TypesTest { assertTypeParameters(UUIDToStringConverter.class, Converter.class, UUID.class, String.class); } - private static void assertTypeParameters(Class<?> klass, Class<?> parameterizedClass, Type... types) { - ParameterizedType parameterizedType = Types.findParameterizedType(klass, parameterizedClass); + private static void assertTypeParameters(final Class<?> klass, + final Class<?> parameterizedClass, + final Type... types) { + ParameterizedType parameterizedType = new Types().findParameterizedType(klass, parameterizedClass); Assert.assertNotNull(parameterizedType); Assert.assertEquals(parameterizedType.getRawType(), parameterizedClass); Assert.assertArrayEquals(types, parameterizedType.getActualTypeArguments()); diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java index 7189a5d..70e3a45 100644 --- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java +++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java @@ -253,6 +253,7 @@ public class JohnzonBuilder implements JsonbBuilder { getBeanManager(); // force detection + final Types types = new Types(); builder.setReadAttributeBeforeWrite( config.getProperty("johnzon.readAttributeBeforeWrite").map(Boolean.class::cast).orElse(false)); builder.setAutoAdjustStringBuffers( @@ -275,7 +276,7 @@ public class JohnzonBuilder implements JsonbBuilder { config.getProperty(JsonbConfig.SERIALIZERS).map(JsonbSerializer[].class::cast).ifPresent(serializers -> { Stream.of(serializers).forEach(s -> { - final ParameterizedType pt = Types.findParameterizedType(s.getClass(), JsonbSerializer.class); + final ParameterizedType pt = types.findParameterizedType(s.getClass(), JsonbSerializer.class); final Type[] args = pt.getActualTypeArguments(); // TODO: support PT in ObjectConverter (list) if (args.length != 1 || !Class.class.isInstance(args[0])) { @@ -290,7 +291,7 @@ public class JohnzonBuilder implements JsonbBuilder { }); config.getProperty(JsonbConfig.DESERIALIZERS).map(JsonbDeserializer[].class::cast).ifPresent(deserializers -> { Stream.of(deserializers).forEach(d -> { - final ParameterizedType pt = Types.findParameterizedType(d.getClass(), JsonbDeserializer.class); + final ParameterizedType pt = types.findParameterizedType(d.getClass(), JsonbDeserializer.class); final Type[] args = pt.getActualTypeArguments(); if (args.length != 1 || !Class.class.isInstance(args[0])) { throw new IllegalArgumentException("We only support deserializer on Class for now"); 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 c0fcd33..f9d0ce2 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 @@ -18,52 +18,11 @@ */ package org.apache.johnzon.jsonb; -import org.apache.johnzon.core.Types; -import org.apache.johnzon.jsonb.converter.JohnzonJsonbAdapter; -import org.apache.johnzon.jsonb.converter.JsonbDateConverter; -import org.apache.johnzon.jsonb.converter.JsonbLocalDateConverter; -import org.apache.johnzon.jsonb.converter.JsonbLocalDateTimeConverter; -import org.apache.johnzon.jsonb.converter.JsonbNumberConverter; -import org.apache.johnzon.jsonb.converter.JsonbValueConverter; -import org.apache.johnzon.jsonb.converter.JsonbZonedDateTimeConverter; -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.Converter; -import org.apache.johnzon.mapper.JohnzonAny; -import org.apache.johnzon.mapper.JohnzonConverter; -import org.apache.johnzon.mapper.MapperConverter; -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.Meta; -import org.apache.johnzon.mapper.access.MethodAccessMode; -import org.apache.johnzon.mapper.converter.ReversedAdapter; -import org.apache.johnzon.mapper.internal.AdapterKey; -import org.apache.johnzon.mapper.internal.ConverterAdapter; - -import javax.json.bind.JsonbException; -import javax.json.bind.adapter.JsonbAdapter; -import javax.json.bind.annotation.JsonbCreator; -import javax.json.bind.annotation.JsonbDateFormat; -import javax.json.bind.annotation.JsonbNillable; -import javax.json.bind.annotation.JsonbNumberFormat; -import javax.json.bind.annotation.JsonbProperty; -import javax.json.bind.annotation.JsonbPropertyOrder; -import javax.json.bind.annotation.JsonbTransient; -import javax.json.bind.annotation.JsonbTypeAdapter; -import javax.json.bind.annotation.JsonbTypeDeserializer; -import javax.json.bind.annotation.JsonbTypeSerializer; -import javax.json.bind.config.PropertyNamingStrategy; -import javax.json.bind.config.PropertyOrderStrategy; -import javax.json.bind.config.PropertyVisibilityStrategy; -import javax.json.bind.serializer.JsonbDeserializer; -import javax.json.bind.serializer.JsonbSerializer; -import javax.json.stream.JsonParserFactory; +import static java.util.Arrays.asList; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.apache.johnzon.mapper.reflection.Converters.matches; import java.io.Closeable; import java.io.IOException; @@ -90,18 +49,66 @@ import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.OptionalLong; +import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collector; import java.util.stream.Stream; -import static java.util.Arrays.asList; -import static java.util.Optional.ofNullable; -import static org.apache.johnzon.mapper.reflection.Converters.matches; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.bind.adapter.JsonbAdapter; +import javax.json.bind.annotation.JsonbCreator; +import javax.json.bind.annotation.JsonbDateFormat; +import javax.json.bind.annotation.JsonbNillable; +import javax.json.bind.annotation.JsonbNumberFormat; +import javax.json.bind.annotation.JsonbProperty; +import javax.json.bind.annotation.JsonbPropertyOrder; +import javax.json.bind.annotation.JsonbTransient; +import javax.json.bind.annotation.JsonbTypeAdapter; +import javax.json.bind.annotation.JsonbTypeDeserializer; +import javax.json.bind.annotation.JsonbTypeSerializer; +import javax.json.bind.config.PropertyNamingStrategy; +import javax.json.bind.config.PropertyOrderStrategy; +import javax.json.bind.config.PropertyVisibilityStrategy; +import javax.json.bind.serializer.JsonbDeserializer; +import javax.json.bind.serializer.JsonbSerializer; +import javax.json.stream.JsonParserFactory; + +import org.apache.johnzon.core.Types; +import org.apache.johnzon.jsonb.converter.JohnzonJsonbAdapter; +import org.apache.johnzon.jsonb.converter.JsonbDateConverter; +import org.apache.johnzon.jsonb.converter.JsonbLocalDateConverter; +import org.apache.johnzon.jsonb.converter.JsonbLocalDateTimeConverter; +import org.apache.johnzon.jsonb.converter.JsonbNumberConverter; +import org.apache.johnzon.jsonb.converter.JsonbValueConverter; +import org.apache.johnzon.jsonb.converter.JsonbZonedDateTimeConverter; +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.Converter; +import org.apache.johnzon.mapper.JohnzonAny; +import org.apache.johnzon.mapper.JohnzonConverter; +import org.apache.johnzon.mapper.MapperConverter; +import org.apache.johnzon.mapper.MappingParser; +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.Meta; +import org.apache.johnzon.mapper.access.MethodAccessMode; +import org.apache.johnzon.mapper.converter.ReversedAdapter; +import org.apache.johnzon.mapper.internal.AdapterKey; +import org.apache.johnzon.mapper.internal.ConverterAdapter; public class JsonbAccessMode implements AccessMode, Closeable { private final PropertyNamingStrategy naming; @@ -126,6 +133,7 @@ public class JsonbAccessMode implements AccessMode, Closeable { } }; private boolean failOnMissingCreatorValues; + private final Types types = new Types(); public JsonbAccessMode(final PropertyNamingStrategy propertyNamingStrategy, final String orderValue, final PropertyVisibilityStrategy visibilityStrategy, final boolean caseSensitive, @@ -208,7 +216,8 @@ public class JsonbAccessMode implements AccessMode, Closeable { try { if (adapter != null) { - final Adapter converter = toConverter(parameter.getType(), adapter, dateFormat, numberFormat); + final Adapter converter = toConverter( + this.types, parameter.getType(), adapter, dateFormat, numberFormat); if (matches(parameter.getParameterizedType(), converter)) { converters[i] = converter; itemConverters[i] = null; @@ -331,13 +340,13 @@ public class JsonbAccessMode implements AccessMode, Closeable { } } - private Adapter<?, ?> toConverter(final Type type, + private Adapter<?, ?> toConverter(final Types types, final Type type, final JsonbTypeAdapter adapter, final JsonbDateFormat dateFormat, - final JsonbNumberFormat numberFormat) throws InstantiationException, IllegalAccessException { + final JsonbNumberFormat numberFormat) { final Adapter converter; if (adapter != null) { final Class<? extends JsonbAdapter> value = adapter.value(); - final ParameterizedType pt = Types.findParameterizedType(value, JsonbAdapter.class); + final ParameterizedType pt = types.findParameterizedType(value, JsonbAdapter.class); final JohnzonAdapterFactory.Instance<? extends JsonbAdapter> instance = newInstance(value); toRelease.add(instance); final Type[] actualTypeArguments = pt.getActualTypeArguments(); @@ -411,7 +420,7 @@ public class JsonbAccessMode implements AccessMode, Closeable { reader = finalReader::read; } - final WriterConverters writerConverters = new WriterConverters(initialReader); + final WriterConverters writerConverters = new WriterConverters(initialReader, types); final JsonbProperty property = initialReader.getAnnotation(JsonbProperty.class); final JsonbNillable nillable = initialReader.getClassOrPackageAnnotation(JsonbNillable.class); final boolean isNillable = nillable != null || (property != null && property.nillable()); @@ -604,7 +613,7 @@ public class JsonbAccessMode implements AccessMode, Closeable { private ParsingCacheEntry getClassEntry(final Class<?> clazz) { ParsingCacheEntry cache = parsingCache.get(clazz); if (cache == null) { - cache = new ParsingCacheEntry(new ClassDecoratedType(clazz)); + cache = new ParsingCacheEntry(new ClassDecoratedType(clazz), types); parsingCache.putIfAbsent(clazz, cache); } return cache; @@ -719,21 +728,60 @@ public class JsonbAccessMode implements AccessMode, Closeable { final JohnzonConverter johnzonConverter = annotationHolder.getAnnotation(JohnzonConverter.class); validateAnnotations(annotationHolder, adapter, dateFormat, numberFormat, johnzonConverter); - try { - converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ? - defaultConverters.get(new AdapterKey(annotationHolder.getType(), String.class)) : - toConverter(annotationHolder.getType(), adapter, dateFormat, numberFormat); - } catch (final InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException(e); - } + converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ? + defaultConverters.get(new AdapterKey(annotationHolder.getType(), String.class)) : + toConverter(types, annotationHolder.getType(), adapter, dateFormat, numberFormat); if (deserializer != null) { final Class<? extends JsonbDeserializer> value = deserializer.value(); - final ParameterizedType pt = Types.findParameterizedType(value, JsonbDeserializer.class); final JohnzonAdapterFactory.Instance<? extends JsonbDeserializer> instance = newInstance(value); + final ParameterizedType pt = types.findParameterizedType(value, JsonbDeserializer.class); + final Class<?> mappedType = types.findParamType(pt, JsonbDeserializer.class); toRelease.add(instance); - reader = (jsonObject, targetType, parser) -> - instance.getValue().deserialize(JsonValueParserAdapter.createFor(jsonObject, parserFactory), new JohnzonDeserializationContext(parser), targetType); + reader = new ObjectConverter.Reader() { + private final ConcurrentMap<Type, BiFunction<JsonValue, MappingParser, Object>> impl = + new ConcurrentHashMap<>(); + + @Override + public Object fromJson(final JsonValue value, + final Type targetType, + final MappingParser parser) { + final JsonbDeserializer jsonbDeserializer = instance.getValue(); + if (targetType == mappedType) { // fast test and matches most cases + return mapItem(value, targetType, parser, jsonbDeserializer); + } + + BiFunction<JsonValue, MappingParser, Object> fn = impl.get(targetType); + if (fn == null) { + if (value.getValueType() == JsonValue.ValueType.ARRAY) { + if (ParameterizedType.class.isInstance(targetType)) { + final ParameterizedType parameterizedType = ParameterizedType.class.cast(targetType); + final Class<?> paramType = types.findParamType(parameterizedType, Collection.class); + if (paramType != null && (mappedType == null /*Object*/ || mappedType.isAssignableFrom(paramType))) { + final Collector<Object, ?, ? extends Collection<Object>> collector = + Set.class.isAssignableFrom( + types.asClass(parameterizedType.getRawType())) ? toSet() : toList(); + fn = (json, mp) -> json.asJsonArray().stream() + .map(i -> mapItem(i, paramType, mp, jsonbDeserializer)) + .collect(collector); + } + } + } + if (fn == null) { + fn = (json, mp) -> mapItem(json, targetType, mp, jsonbDeserializer); + } + impl.putIfAbsent(targetType, fn); + } + return fn.apply(value, parser); + } + + private Object mapItem(final JsonValue jsonValue, final Type targetType, + final MappingParser parser, final JsonbDeserializer jsonbDeserializer) { + return jsonbDeserializer.deserialize( + JsonValueParserAdapter.createFor(jsonValue, parserFactory), + new JohnzonDeserializationContext(parser), targetType); + } + }; } else if (johnzonConverter != null) { try { MapperConverter mapperConverter = johnzonConverter.value().newInstance(); @@ -753,7 +801,7 @@ public class JsonbAccessMode implements AccessMode, Closeable { private Adapter<?, ?> converter; private ObjectConverter.Writer writer; - WriterConverters(final DecoratedType initialReader) { + WriterConverters(final DecoratedType initialReader, final Types types) { final JsonbTypeSerializer serializer = initialReader.getAnnotation(JsonbTypeSerializer.class); final JsonbTypeAdapter adapter = initialReader.getAnnotation(JsonbTypeAdapter.class); final JsonbDateFormat dateFormat = initialReader.getAnnotation(JsonbDateFormat.class); @@ -761,17 +809,12 @@ public class JsonbAccessMode implements AccessMode, Closeable { final JohnzonConverter johnzonConverter = initialReader.getAnnotation(JohnzonConverter.class); validateAnnotations(initialReader, adapter, dateFormat, numberFormat, johnzonConverter); - try { - converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ? - defaultConverters.get(new AdapterKey(initialReader.getType(), String.class)) : - toConverter(initialReader.getType(), adapter, dateFormat, numberFormat); - } catch (final InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException(e); - } + converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ? + defaultConverters.get(new AdapterKey(initialReader.getType(), String.class)) : + toConverter(types, initialReader.getType(), adapter, dateFormat, numberFormat); if (serializer != null) { final Class<? extends JsonbSerializer> value = serializer.value(); - final ParameterizedType pt = Types.findParameterizedType(value, JsonbSerializer.class); final JohnzonAdapterFactory.Instance<? extends JsonbSerializer> instance = newInstance(value); toRelease.add(instance); writer = (instance1, jsonbGenerator) -> @@ -828,9 +871,9 @@ public class JsonbAccessMode implements AccessMode, Closeable { private final ReaderConverters readers; private final WriterConverters writers; - ParsingCacheEntry(final DecoratedType type) { + ParsingCacheEntry(final DecoratedType type, final Types types) { readers = new ReaderConverters(type); - writers = new WriterConverters(type); + writers = new WriterConverters(type, types); } } } diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/MoreTests.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SeriaizersRoundTripTest.java similarity index 64% rename from johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/MoreTests.java rename to johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SeriaizersRoundTripTest.java index f813cef..c87ab3a 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/MoreTests.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SeriaizersRoundTripTest.java @@ -18,8 +18,10 @@ */ package org.apache.johnzon.jsonb; -import java.io.StringWriter; +import static org.junit.Assert.assertEquals; + import java.lang.reflect.Type; +import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -40,7 +42,7 @@ import javax.json.stream.JsonParser; import org.junit.Test; -public class MoreTests { +public class SeriaizersRoundTripTest { public enum Color { @@ -66,6 +68,28 @@ public class MoreTests { public static Option of(boolean value) { return value ? YES : NO; } + + @Override + public String toString() { + return "Option{value=" + value + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Option option = (Option) o; + return value == option.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } } public static class VATNumber { @@ -79,6 +103,28 @@ public class MoreTests { public long getValue() { return value; } + + @Override + public String toString() { + return "VATNumber{value=" + value + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VATNumber vatNumber = (VATNumber) o; + return value == vatNumber.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } } public interface Composite<T, X> extends JsonbSerializer<T>, JsonbDeserializer<T>, JsonbAdapter<T, X> {} @@ -171,36 +217,66 @@ public class MoreTests { @JsonbTypeSerializer(UUIDComposite.class) @JsonbTypeDeserializer(UUIDComposite.class) - public UUID uuid = UUID.randomUUID(); + public UUID uuid; @JsonbTypeAdapter(UUIDComposite.class) - public UUID uuid2 = UUID.randomUUID(); + public UUID uuid2; @JsonbTypeSerializer(OptionDeSer.class) @JsonbTypeDeserializer(OptionDeSer.class) - public Option option = Option.YES; + public Option option; @JsonbTypeSerializer(VATDeSer.class) @JsonbTypeDeserializer(VATDeSer.class) - public VATNumber vatNumber = new VATNumber(42); + public VATNumber vatNumber; -// @JsonbTypeSerializer(CharsDeSer.class) -// @JsonbTypeDeserializer(CharsDeSer.class) - // TODO Not working as @JsonbTypeSerializer seems to be ignored ("hello world" instead of ["h", "e"...]) - public String hello = "hello world"; + @JsonbTypeSerializer(CharsDeSer.class) + @JsonbTypeDeserializer(CharsDeSer.class) + public String hello; -// @JsonbTypeSerializer(ColorDeSer.class) -// @JsonbTypeDeserializer(ColorDeSer.class) - // TODO Not working as @JsonbTypeSerializer seems to be ignored ("GREEN" instead of "G") - public Color color = Color.GREEN; - + @JsonbTypeSerializer(ColorDeSer.class) + @JsonbTypeDeserializer(ColorDeSer.class) + public Color color; + + @Override + public String toString() { + return "Wrapper{uuid=" + uuid + ", uuid2=" + uuid2 + ", option=" + option + + ", vatNumber=" + vatNumber + ", hello='" + hello + '\'' + ", color=" + color + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final Wrapper wrapper = (Wrapper) o; + return Objects.equals(uuid, wrapper.uuid) && Objects.equals(uuid2, wrapper.uuid2) && Objects.equals(option, + wrapper.option) && Objects.equals(vatNumber, wrapper.vatNumber) && Objects.equals(hello, + wrapper.hello) && color == wrapper.color; + } + + @Override + public int hashCode() { + return Objects.hash(uuid, uuid2, option, vatNumber, hello, color); + } } @Test - public void testIt() { - Jsonb jsonb = JsonbBuilder.create(); - StringWriter w = new StringWriter(); - jsonb.toJson(new Wrapper(), w); - jsonb.fromJson(w.toString(), Wrapper.class); + public void roundTrip() throws Exception { + final Wrapper original = new Wrapper(); + original.uuid = UUID.randomUUID(); + original.uuid2 = UUID.randomUUID(); + original.option = Option.YES; + original.vatNumber = new VATNumber(42); + original.hello = "hello world"; + original.color = Color.GREEN; + + try (final Jsonb jsonb = JsonbBuilder.create()) { + final Wrapper deserialized = jsonb.fromJson(jsonb.toJson(original), Wrapper.class); + assertEquals(original, deserialized); + } } } diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java index fff01e6..f8598de 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java @@ -68,7 +68,8 @@ public class DynamicMappingGenerator implements MappingGenerator { } private enum WritingState { - NONE, WROTE_START_OBJECT, PRIMITIVE + NONE, WROTE_START_OBJECT, + DONT_WRITE_END } private static class InObjectOrPrimitiveJsonGenerator implements JsonGenerator { @@ -105,6 +106,10 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator writeStartArray() { + if (keyIfNoObject != null && state == WritingState.NONE) { + state = WritingState.DONT_WRITE_END; // skip writeEnd since the impl will do it + return delegate.writeStartArray(keyIfNoObject); + } return delegate.writeStartArray(); } @@ -182,7 +187,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final JsonValue value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -191,7 +196,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final String value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -200,7 +205,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final BigDecimal value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -209,7 +214,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final BigInteger value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -218,7 +223,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final int value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -227,7 +232,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final long value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -236,7 +241,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(final double value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -245,7 +250,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator write(boolean value) { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.write(keyIfNoObject, value); } return delegate.write(value); @@ -254,7 +259,7 @@ public class DynamicMappingGenerator implements MappingGenerator { @Override public JsonGenerator writeNull() { if (isWritingPrimitive()) { - state = WritingState.PRIMITIVE; + state = WritingState.DONT_WRITE_END; return delegate.writeNull(keyIfNoObject); } return delegate.writeNull(); 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 1f5a0a3..b7d1629 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 @@ -371,8 +371,23 @@ public class MappingGeneratorImpl implements MappingGenerator { writeMapBody((Map<?, ?>) value, itemConverter); generator.writeEnd(); } else if (primitive || (dynamic && Mappings.isPrimitive(type))) { - writePrimitives(key, type, value, generator); + if (objectConverter != null) { + final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key); + objectConverter.writeJson(value, dynamicMappingGenerator); + dynamicMappingGenerator.flushIfNeeded(); + } else { + writePrimitives(key, type, value, generator); + } } else { + if (objectConverter != null) { + final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key); + objectConverter.writeJson(value, dynamicMappingGenerator); + dynamicMappingGenerator.flushIfNeeded(); + return; + } + final Adapter converter = config.findAdapter(type); if (converter != null) { final Object adapted = doConvertFrom(value, converter); 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 198c573..63549a4 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 @@ -335,7 +335,9 @@ public class MappingParserImpl implements MappingParser { } } } - final Object convertedValue = toValue(existingInstance, jsonValue, value.converter, value.itemConverter, value.paramType, value.objectConverter, + final Object convertedValue = toValue( + existingInstance, jsonValue, value.converter, value.itemConverter, + value.paramType, value.objectConverter, new JsonPointerTracker(jsonPointer, setter.getKey()), inType); if (convertedValue != null) { setterMethod.write(t, convertedValue); @@ -727,12 +729,7 @@ public class MappingParserImpl implements MappingParser { final JsonPointerTracker jsonPointer, final Type rootType) { if (objectConverter != null) { - - if (jsonValue instanceof JsonArray) { - return buildArray(type, jsonValue.asJsonArray(), itemConverter, objectConverter, jsonPointer, rootType); - } else { - return objectConverter.fromJson(jsonValue, type, this); - } + return objectConverter.fromJson(jsonValue, type, this); } try {
