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
The following commit(s) were added to refs/heads/master by this push: new 12c59fd [JOHNZON-321] ensure enums mapped with jsonb can use @JsonbProperty to rename the enum name in json land 12c59fd is described below commit 12c59fd769101c972a5ceb1b9d5a3e05b8a145df Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Wed Aug 19 09:30:48 2020 +0200 [JOHNZON-321] ensure enums mapped with jsonb can use @JsonbProperty to rename the enum name in json land --- .../org/apache/johnzon/jsonb/JohnzonBuilder.java | 7 +++ .../johnzon/jsonb/adapter/JsonbEnumAdapter.java | 42 ++++++++++++---- .../apache/johnzon/jsonb/EnumConverterTest.java | 56 ++++++++++++++++++++++ .../java/org/apache/johnzon/mapper/Mapper.java | 38 +++++++-------- .../org/apache/johnzon/mapper/MapperBuilder.java | 9 +++- .../org/apache/johnzon/mapper/MapperConfig.java | 16 +++++-- .../apache/johnzon/mapper/MappingParserImpl.java | 3 +- .../java/org/apache/johnzon/mapper/Mappings.java | 3 +- .../johnzon/mapper/converter/EnumConverter.java | 4 +- .../apache/johnzon/mapper/MapperConfigTest.java | 4 +- .../test/java/org/superbiz/ExtendMappingTest.java | 4 +- 11 files changed, 147 insertions(+), 39 deletions(-) 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 5bbfa2c..ab82053 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 @@ -22,6 +22,7 @@ import org.apache.johnzon.core.AbstractJsonFactory; import org.apache.johnzon.core.JsonGeneratorFactoryImpl; import org.apache.johnzon.core.JsonParserFactoryImpl; import org.apache.johnzon.core.Types; +import org.apache.johnzon.jsonb.adapter.JsonbEnumAdapter; import org.apache.johnzon.jsonb.api.experimental.PolymorphicConfig; import org.apache.johnzon.jsonb.cdi.CDIs; import org.apache.johnzon.jsonb.converter.JohnzonJsonbAdapter; @@ -32,6 +33,7 @@ import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory; import org.apache.johnzon.mapper.Converter; import org.apache.johnzon.mapper.Mapper; import org.apache.johnzon.mapper.MapperBuilder; +import org.apache.johnzon.mapper.MapperConfig; import org.apache.johnzon.mapper.ObjectConverter; import org.apache.johnzon.mapper.SerializeValueFilter; import org.apache.johnzon.mapper.access.AccessMode; @@ -96,6 +98,7 @@ public class JohnzonBuilder implements JsonbBuilder { @Override public Jsonb build() { + builder.setEnumConverterFactory(type -> newEnumConverter(Class.class.cast(type))); if (jsonp != null) { builder.setGeneratorFactory(jsonp.createGeneratorFactory(generatorConfig())); builder.setReaderFactory(jsonp.createReaderFactory(readerConfig())); @@ -335,6 +338,10 @@ public class JohnzonBuilder implements JsonbBuilder { return doCreateJsonb(skipCdi, ijson, builder.build()); } + private <T extends Enum<T>> MapperConfig.CustomEnumConverter<T> newEnumConverter(final Class<T> enumType) { + return new JsonbEnumAdapter<>(enumType); + } + // note: this method must stay as small as possible to enable graalvm to replace it by "false" when needed private Jsonb doCreateJsonb(final boolean skipCdi, final boolean ijson, final Mapper mapper) { if (!skipCdi && cdiIntegration != null && cdiIntegration.isCanWrite()) { diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/adapter/JsonbEnumAdapter.java similarity index 52% copy from johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java copy to johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/adapter/JsonbEnumAdapter.java index 4000183..4a23c0b 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java +++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/adapter/JsonbEnumAdapter.java @@ -16,40 +16,53 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.johnzon.mapper.converter; +package org.apache.johnzon.jsonb.adapter; -import org.apache.johnzon.mapper.Converter; +import org.apache.johnzon.mapper.MapperConfig; +import javax.json.bind.annotation.JsonbProperty; +import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; -public class EnumConverter<T extends Enum<T>> implements Converter<T>, Converter.TypeAccess { +public class JsonbEnumAdapter<T extends Enum<T>> implements MapperConfig.CustomEnumConverter<T> { private final Map<String, T> values; + private final Map<T, String> reversed; private final Class<T> enumType; - public EnumConverter(final Class<T> aClass) { + public JsonbEnumAdapter(final Class<T> aClass) { this.enumType = aClass; final T[] enumConstants = aClass.isEnum() ? aClass.getEnumConstants() : (T[]) aClass.getSuperclass().getEnumConstants(); values = new HashMap<>(enumConstants.length); + reversed = new HashMap<>(enumConstants.length); for (final T t : enumConstants) { - values.put(t.name(), t); + try { + final Field field = findField(aClass, t.name()); + final JsonbProperty property = field.getAnnotation(JsonbProperty.class); + final String name = property == null || property.value().isEmpty() ? t.name() : property.value(); + values.put(name, t); + reversed.put(t, name); + } catch (final Exception e) { + values.put(t.name(), t); + reversed.put(t, t.name()); + } } } @Override // no need of cache here, it is already fast public String toString(final T instance) { - return instance != null ? instance.name() : null; + return instance != null ? reversed.get(instance) : null; } @Override public T fromString(final String text) { - T val = values.get(text); + final T val = values.get(text); if (val == null) { - throw new IllegalArgumentException("Illegal " + enumType + " enum value: " + text); + throw new IllegalArgumentException("Illegal " + enumType + " enum value: " + text + ", known values: " + values.keySet()); } return val; } @@ -63,4 +76,17 @@ public class EnumConverter<T extends Enum<T>> implements Converter<T>, Converter public Type type() { return enumType; } + + private Field findField(final Class<T> impl, final String field) { + Class<?> type = impl; + while (type != null && type != Object.class && type != Enum.class) { + try { + return type.getDeclaredField(field); + } catch (final NoSuchFieldException e) { + // continue + } + type = type.getSuperclass(); + } + throw new IllegalArgumentException("Missing field: " + field); + } } diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/EnumConverterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/EnumConverterTest.java new file mode 100644 index 0000000..7e081e5 --- /dev/null +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/EnumConverterTest.java @@ -0,0 +1,56 @@ +/* + * 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.jsonb; + +import org.apache.johnzon.jsonb.test.JsonbRule; +import org.junit.Rule; +import org.junit.Test; + +import javax.json.bind.annotation.JsonbProperty; + +import static org.junit.Assert.assertEquals; + +public class EnumConverterTest { + @Rule + public final JsonbRule jsonb = new JsonbRule(); + + @Test + public void fromEnumToString() { + assertEquals(AnEnum.B, jsonb.fromJson("{\"value\":\"-2\"}", Wrapper.class).value); + } + + @Test + public void fromStringToEnum() { + final Wrapper wrapper = new Wrapper(); + wrapper.value = AnEnum.A; + assertEquals("{\"value\":\"-1\"}", jsonb.toJson(wrapper)); + } + + public static class Wrapper { + public AnEnum value; + } + + public enum AnEnum { + @JsonbProperty("-1") + A, + + @JsonbProperty("-2") + B + } +} 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 391c60f..709801f 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 @@ -18,8 +18,22 @@ */ package org.apache.johnzon.mapper; -import static org.apache.johnzon.mapper.internal.Streams.noClose; +import org.apache.johnzon.mapper.internal.JsonPointerTracker; +import org.apache.johnzon.mapper.reflection.JohnzonCollectionType; +import org.apache.johnzon.mapper.util.ArrayUtil; +import javax.json.JsonArray; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonReaderFactory; +import javax.json.JsonString; +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.spi.JsonProvider; +import javax.json.stream.JsonGenerator; +import javax.json.stream.JsonGeneratorFactory; +import javax.json.stream.JsonParser; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -39,22 +53,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import javax.json.JsonArray; -import javax.json.JsonBuilderFactory; -import javax.json.JsonObject; -import javax.json.JsonReader; -import javax.json.JsonReaderFactory; -import javax.json.JsonString; -import javax.json.JsonStructure; -import javax.json.JsonValue; -import javax.json.spi.JsonProvider; -import javax.json.stream.JsonGenerator; -import javax.json.stream.JsonGeneratorFactory; -import javax.json.stream.JsonParser; - -import org.apache.johnzon.mapper.internal.JsonPointerTracker; -import org.apache.johnzon.mapper.reflection.JohnzonCollectionType; -import org.apache.johnzon.mapper.util.ArrayUtil; +import static org.apache.johnzon.mapper.internal.Streams.noClose; public class Mapper implements Closeable { @@ -80,7 +79,6 @@ public class Mapper implements Closeable { this.charset = config.getEncoding(); } - public <T> void writeArray(final Object object, final OutputStream stream) { if (object instanceof short[]) { writeObject(ArrayUtil.asList((short[]) object), stream); @@ -401,7 +399,9 @@ public class Mapper implements Closeable { } private boolean isDedup(final Type clazz) { - if (clazz instanceof Class) { + if (clazz instanceof Class && + JsonValue.class != clazz && JsonStructure.class != clazz && + JsonObject.class != clazz && JsonArray.class != clazz) { return isDeduplicateObjects((Class) clazz); } return false; 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 ade0e26..171f93a 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 @@ -33,6 +33,7 @@ import org.apache.johnzon.mapper.converter.ByteConverter; import org.apache.johnzon.mapper.converter.CachedDelegateConverter; import org.apache.johnzon.mapper.converter.CharacterConverter; import org.apache.johnzon.mapper.converter.DoubleConverter; +import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.converter.FloatConverter; import org.apache.johnzon.mapper.converter.IntegerConverter; import org.apache.johnzon.mapper.converter.LongConverter; @@ -103,6 +104,7 @@ public class MapperBuilder { private boolean useJsRange; private boolean useBigDecimalForObjectNumbers; private boolean supportEnumContainerDeserialization = true; + private Function<Class<?>, MapperConfig.CustomEnumConverter<?>> enumConverterFactory = type -> new EnumConverter(type); // @experimental polymorphic api private Function<String, Class<?>> typeLoader; @@ -229,10 +231,15 @@ public class MapperBuilder { interfaceImplementationMapping, useJsRange, useBigDecimalForObjectNumbers, supportEnumContainerDeserialization, typeLoader, discriminatorMapper, discriminator, - deserializationPredicate, serializationPredicate), + deserializationPredicate, serializationPredicate, + enumConverterFactory), closeables); } + public void setEnumConverterFactory(final Function<Class<?>, MapperConfig.CustomEnumConverter<?>> enumConverterFactory) { + this.enumConverterFactory = enumConverterFactory; + } + public ConcurrentHashMap<AdapterKey, Adapter<?,?>> getAdapters() { return adapters; } 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 d0b17d5..6e2906b 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 @@ -19,7 +19,6 @@ package org.apache.johnzon.mapper; import org.apache.johnzon.mapper.access.AccessMode; -import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.internal.AdapterKey; import org.apache.johnzon.mapper.internal.ConverterAdapter; import org.apache.johnzon.mapper.map.LazyConverterMap; @@ -94,6 +93,8 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { private final Collection<Type> noParserAdapterTypes = new ConcurrentHashMap<Type, Boolean>().keySet(true); private final Collection<Type> noGeneratorAdapterTypes = new ConcurrentHashMap<Type, Boolean>().keySet(true); + private final Function<Class<?>, CustomEnumConverter<?>> enumConverterFactory; + //disable checkstyle for 10+ parameters //CHECKSTYLE:OFF public MapperConfig(final LazyConverterMap adapters, @@ -117,7 +118,8 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { final Function<Class<?>, String> discriminatorMapper, final String discriminator, final Predicate<Class<?>> deserializationPredicate, - final Predicate<Class<?>> serializationPredicate) { + final Predicate<Class<?>> serializationPredicate, + final Function<Class<?>, CustomEnumConverter<?>> enumConverterFactory) { //CHECKSTYLE:ON this.objectConverterWriters = objectConverterWriters; this.objectConverterReaders = objectConverterReaders; @@ -138,6 +140,7 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { this.serializationPredicate = serializationPredicate; this.deserializationPredicate = deserializationPredicate; this.discriminator = discriminator; + this.enumConverterFactory = enumConverterFactory; // handle Adapters this.adapters = adapters; @@ -157,6 +160,10 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { this.deduplicateObjects = deduplicateObjects; } + public Function<Class<?>, CustomEnumConverter<?>> getEnumConverterFactory() { + return enumConverterFactory; + } + public Collection<Type> getNoParserAdapterTypes() { return noParserAdapterTypes; } @@ -219,7 +226,7 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { if (Class.class.isInstance(aClass)) { final Class<?> clazz = Class.class.cast(aClass); if (Enum.class.isAssignableFrom(clazz)) { - final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz), clazz); + final Adapter<?, ?> enumConverter = new ConverterAdapter(enumConverterFactory.apply(clazz), clazz); adapters.putIfAbsent(new AdapterKey(String.class, aClass), enumConverter); return enumConverter; } @@ -418,4 +425,7 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { public boolean isSupportEnumContainerDeserialization() { return supportEnumMapDeserialization; } + + public interface CustomEnumConverter<A> extends Converter<A>, Converter.TypeAccess { + } } 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 7acdd67..71e0848 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 @@ -20,7 +20,6 @@ package org.apache.johnzon.mapper; import org.apache.johnzon.mapper.access.AccessMode; import org.apache.johnzon.mapper.converter.CharacterConverter; -import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.internal.AdapterKey; import org.apache.johnzon.mapper.internal.ConverterAdapter; import org.apache.johnzon.mapper.internal.JsonPointerTracker; @@ -1126,7 +1125,7 @@ public class MappingParserImpl implements MappingParser { if (Class.class.isInstance(aClass)) { final Class<?> clazz = Class.class.cast(aClass); if (Enum.class.isAssignableFrom(clazz)) { - final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz), clazz); + final Adapter<?, ?> enumConverter = new ConverterAdapter(config.getEnumConverterFactory().apply(clazz), clazz); config.getAdapters().putIfAbsent(new AdapterKey(String.class, aClass), enumConverter); return enumConverter; } 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 1c5b045..751440d 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 @@ -57,7 +57,6 @@ import org.apache.johnzon.mapper.access.AccessMode; import org.apache.johnzon.mapper.access.FieldAccessMode; 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; import org.apache.johnzon.mapper.internal.ConverterAdapter; import org.apache.johnzon.mapper.reflection.Generics; @@ -699,7 +698,7 @@ public class Mappings { final AdapterKey key = new AdapterKey(String.class, type); converter = adapters.get(key); // first ensure user didnt override it if (converter == null) { - converter = new ConverterAdapter(new EnumConverter(type), type); + converter = new ConverterAdapter(config.getEnumConverterFactory().apply(type), type); adapters.put(key, (Adapter<?, ?>) converter); } } diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java index 4000183..00128b3 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java @@ -18,13 +18,13 @@ */ package org.apache.johnzon.mapper.converter; -import org.apache.johnzon.mapper.Converter; +import org.apache.johnzon.mapper.MapperConfig; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; -public class EnumConverter<T extends Enum<T>> implements Converter<T>, Converter.TypeAccess { +public class EnumConverter<T extends Enum<T>> implements MapperConfig.CustomEnumConverter<T> { private final Map<String, T> values; private final Class<T> enumType; 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 4044781..506e74e 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 @@ -21,6 +21,7 @@ package org.apache.johnzon.mapper; import static java.util.Collections.emptyMap; import org.apache.johnzon.mapper.access.FieldAccessMode; +import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.map.LazyConverterMap; import org.junit.Assert; import org.junit.Test; @@ -169,7 +170,8 @@ public class MapperConfigTest { StandardCharsets.UTF_8, null, false, false, null, false, false, emptyMap(), true, false, true, - null, null, null, null, null); + null, null, null, null, null, + type -> new EnumConverter(type)); } diff --git a/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java b/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java index 27fdd0d..fb6ccf8 100644 --- a/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java +++ b/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java @@ -21,6 +21,7 @@ package org.superbiz; import org.apache.johnzon.mapper.MapperConfig; import org.apache.johnzon.mapper.Mappings; import org.apache.johnzon.mapper.access.FieldAccessMode; +import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.map.LazyConverterMap; import org.junit.Test; @@ -62,7 +63,8 @@ public class ExtendMappingTest { new FieldAccessMode(false, false), StandardCharsets.UTF_8, String::compareTo, false, false, null, false, false, emptyMap(), true, false, true, - null, null, null, null, null)); + null, null, null, null, null, + type -> new EnumConverter(type))); } @Override