diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java index 94b1dba6..d85d399f 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.datatype.guava; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; import com.google.common.base.Optional; import com.google.common.collect.*; import com.google.common.hash.HashCode; @@ -21,6 +22,8 @@ import java.io.Serializable; +import static com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes.isAssignableFromPrimitive; + /** * Custom deserializers module offers. */ @@ -127,7 +130,9 @@ public ValueDeserializer findCollectionDeserializer(CollectionType type, null, null); } - return null; + return isAssignableFromPrimitive(raw) + .transform(PrimitiveTypes.Primitives::newDeserializer) + .orNull(); } private void requireCollectionOfComparableElements(CollectionType actualType, String targetType) { @@ -310,8 +315,10 @@ public boolean hasDeserializerFor(DeserializationConfig config, Class valueTy || ImmutableCollection.class.isAssignableFrom(valueType) || ImmutableMap.class.isAssignableFrom(valueType) || BiMap.class.isAssignableFrom(valueType) + || isAssignableFromPrimitive(valueType).isPresent() ; } return false; } + } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java index 0032f5ba..49c1b15e 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java @@ -1,5 +1,7 @@ package com.fasterxml.jackson.datatype.guava; +import com.fasterxml.jackson.databind.type.CollectionLikeType; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; import java.io.Serializable; import java.util.Set; @@ -48,7 +50,7 @@ public Iterable convert(Object value) { } @Override - public ValueSerializer findReferenceSerializer(SerializationConfig config, + public ValueSerializer findReferenceSerializer(SerializationConfig config, ReferenceType refType, BeanDescription beanDesc, JsonFormat.Value formatOverrides, TypeSerializer contentTypeSerializer, ValueSerializer contentValueSerializer) { @@ -114,8 +116,21 @@ public ValueSerializer findMapLikeSerializer(SerializationConfig config, return null; } + @Override + public ValueSerializer findCollectionLikeSerializer(SerializationConfig config, CollectionLikeType type, + BeanDescription beanDesc, JsonFormat.Value formatOverrides, TypeSerializer elementTypeSerializer, + ValueSerializer elementValueSerializer) + { + Class raw = type.getRawClass(); + Optional> primitiveSerializer = PrimitiveTypes.isAssignableFromPrimitive(raw) + .transform((ignore) -> ToStringSerializer.instance); + + return primitiveSerializer + .or(() -> super.findCollectionLikeSerializer(config, type, beanDesc, formatOverrides, elementTypeSerializer, elementValueSerializer)); + } + private JavaType _findDeclared(JavaType subtype, Class target) { - JavaType decl = subtype.findSuperType(target); + JavaType decl = subtype.findSuperType(target); if (decl == null) { // should never happen but throw new IllegalArgumentException("Strange "+target.getName()+" sub-type, "+subtype+", can not find type parameters"); } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/BasePrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/BasePrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..343d72a1 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/BasePrimitiveCollectionDeserializer.java @@ -0,0 +1,75 @@ +package com.fasterxml.jackson.datatype.guava.deser; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; + +import java.util.Collection; +import java.util.List; + +public abstract class BasePrimitiveCollectionDeserializer, IntermediateCollection extends Collection> + extends StdDeserializer { + + protected BasePrimitiveCollectionDeserializer(Class cls, Class itemType) { + super(cls); + } + + protected BasePrimitiveCollectionDeserializer(JavaType type) { + super(type); + } + + protected abstract IntermediateCollection createIntermediateCollection(); + + protected IntermediateCollection createIntermediateCollection(int expectedSize) { + return createIntermediateCollection(); + } + + protected abstract void add(IntermediateCollection intermediateCollection, JsonParser parser, + DeserializationContext context) throws JacksonException; + + protected abstract PrimitiveList finish(IntermediateCollection intermediateCollection); + + @Override + public Object deserializeWithType(JsonParser parser, DeserializationContext context, + TypeDeserializer typeDeserializer) throws JacksonException { + return typeDeserializer.deserializeTypedFromArray(parser, context); + } + + @SuppressWarnings("unchecked") + @Override + public PrimitiveList deserialize(JsonParser parser, DeserializationContext context) + throws JacksonException { + // Should usually point to START_ARRAY + if (parser.isExpectedStartArrayToken()) { + return _deserializeContents(parser, context); + } + // But may support implicit arrays from single values? + if (context.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) { + return _deserializeFromSingleValue(parser, context); + } + return (PrimitiveList) context.handleUnexpectedToken(getValueType(context), parser); + } + + protected PrimitiveList _deserializeContents(JsonParser parser, DeserializationContext context) + throws JacksonException { + IntermediateCollection collection = createIntermediateCollection(); + + while (parser.nextToken() != JsonToken.END_ARRAY) { + add(collection, parser, context); + } + return finish(collection); + } + + protected PrimitiveList _deserializeFromSingleValue(JsonParser parser, DeserializationContext ctxt) + throws JacksonException { + IntermediateCollection intermediateCollection = createIntermediateCollection(); + add(intermediateCollection, parser, ctxt); + return finish(intermediateCollection); + } + +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BaseGuavaPrimitivesCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BaseGuavaPrimitivesCollectionDeserializer.java new file mode 100644 index 00000000..0b2b6454 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BaseGuavaPrimitivesCollectionDeserializer.java @@ -0,0 +1,30 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.datatype.guava.deser.BasePrimitiveCollectionDeserializer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class BaseGuavaPrimitivesCollectionDeserializer, IntermediateCollection extends Collection> + extends BasePrimitiveCollectionDeserializer { + + protected BaseGuavaPrimitivesCollectionDeserializer(Class cls, Class itemType) { + super(cls, itemType); + } + + @Override + protected IntermediateCollection createIntermediateCollection() { + return (IntermediateCollection) new ArrayList(); + } + + @Override + protected void add(IntermediateCollection intermediateCollection, JsonParser parser, DeserializationContext context) throws JacksonException { + intermediateCollection.add(asPrimitive(parser)); + } + + protected abstract ObjectType asPrimitive(JsonParser parser) throws JacksonException; +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BooleansPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BooleansPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..e633928e --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BooleansPrimitiveCollectionDeserializer.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Booleans; + +import java.util.Collection; +import java.util.List; + +public class BooleansPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + + public BooleansPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.BooleansType, Boolean.class); + } + + @Override + protected Boolean asPrimitive(JsonParser parser) throws JacksonException { + return parser.getBooleanValue(); + } + + @Override + protected List finish(Collection booleans) { + return Booleans.asList(Booleans.toArray(booleans)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BytesPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BytesPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..abc06927 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/BytesPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Bytes; + +import java.util.Collection; +import java.util.List; + +public class BytesPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public BytesPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.BytesType, Byte.class); + } + + @Override + protected Byte asPrimitive(JsonParser parser) throws JacksonException { + return parser.getByteValue(); + } + + @Override + protected List finish(Collection bytes) { + return Bytes.asList(Bytes.toArray(bytes)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/CharsPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/CharsPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..a25b9096 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/CharsPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Chars; + +import java.util.Collection; +import java.util.List; + +public class CharsPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public CharsPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.CharsType, Character.class); + } + + @Override + protected Character asPrimitive(JsonParser parser) throws JacksonException { + return (char) parser.getValueAsString().toCharArray()[0]; + } + + @Override + protected List finish(Collection characters) { + return Chars.asList(Chars.toArray(characters)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/DoublesPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/DoublesPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..51473554 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/DoublesPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Doubles; + +import java.util.Collection; +import java.util.List; + +public class DoublesPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public DoublesPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.DoublesType, Double.class); + } + + @Override + protected Double asPrimitive(JsonParser parser) throws JacksonException { + return parser.getDoubleValue(); + } + + @Override + protected List finish(Collection doubles) { + return Doubles.asList(Doubles.toArray(doubles)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/FloatsPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/FloatsPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..24d56e6d --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/FloatsPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Floats; + +import java.util.Collection; +import java.util.List; + +public class FloatsPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public FloatsPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.FloatsType, Float.class); + } + + @Override + protected Float asPrimitive(JsonParser parser) throws JacksonException { + return parser.getFloatValue(); + } + + @Override + protected List finish(Collection floats) { + return Floats.asList(Floats.toArray(floats)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/IntsPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/IntsPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..604ecd33 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/IntsPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Ints; + +import java.util.Collection; +import java.util.List; + +public class IntsPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public IntsPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.IntsType, Integer.class); + } + + @Override + protected Integer asPrimitive(JsonParser parser) throws JacksonException { + return parser.getIntValue(); + } + + @Override + protected List finish(Collection integers) { + return Ints.asList(Ints.toArray(integers)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/LongsPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/LongsPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..b0a5a8b6 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/LongsPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Longs; + +import java.util.Collection; +import java.util.List; + +public class LongsPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public LongsPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.LongsType, Long.class); + } + + @Override + protected Long asPrimitive(JsonParser parser) throws JacksonException { + return parser.getLongValue(); + } + + @Override + protected List finish(Collection longs) { + return Longs.asList(Longs.toArray(longs)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/ShortsPrimitiveCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/ShortsPrimitiveCollectionDeserializer.java new file mode 100644 index 00000000..ecfc9ae8 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/primitives/ShortsPrimitiveCollectionDeserializer.java @@ -0,0 +1,26 @@ +package com.fasterxml.jackson.datatype.guava.deser.primitives; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Shorts; + +import java.util.Collection; +import java.util.List; + +public class ShortsPrimitiveCollectionDeserializer + extends BaseGuavaPrimitivesCollectionDeserializer, Collection> { + public ShortsPrimitiveCollectionDeserializer() { + super(PrimitiveTypes.ShortsType, Short.class); + } + + @Override + protected Short asPrimitive(JsonParser parser) throws JacksonException { + return parser.getShortValue(); + } + + @Override + protected List finish(Collection shorts) { + return Shorts.asList(Shorts.toArray(shorts)); + } +} \ No newline at end of file diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/util/PrimitiveTypes.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/util/PrimitiveTypes.java new file mode 100644 index 00000000..f9a01cdd --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/util/PrimitiveTypes.java @@ -0,0 +1,152 @@ +package com.fasterxml.jackson.datatype.guava.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.datatype.guava.deser.BasePrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.BooleansPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.BytesPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.CharsPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.DoublesPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.FloatsPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.IntsPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.LongsPrimitiveCollectionDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.primitives.ShortsPrimitiveCollectionDeserializer; +import com.google.common.base.Optional; +import com.google.common.primitives.Booleans; +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Chars; +import com.google.common.primitives.Doubles; +import com.google.common.primitives.Floats; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.common.primitives.Shorts; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; + +/** + * Utility class to cover all primitive types + * + * @author robert@albertlr.ro + */ +public class PrimitiveTypes { + + /** An enum with all the primitives */ + public enum Primitives { + BOOLEAN(BooleansType, boolean.class, Boolean.class, () -> new BooleansPrimitiveCollectionDeserializer()), + BYTE(BytesType, byte.class, Byte.class, () -> new BytesPrimitiveCollectionDeserializer()), + CHAR(CharsType, char.class, Character.class, () -> new CharsPrimitiveCollectionDeserializer()), + DOUBLE(DoublesType, double.class, Double.class, () -> new DoublesPrimitiveCollectionDeserializer()), + FLOAT(FloatsType, float.class, Float.class, () -> new FloatsPrimitiveCollectionDeserializer()), + INT(IntsType, int.class, Integer.class, () -> new IntsPrimitiveCollectionDeserializer()), + LONG(LongsType, long.class, Long.class, () -> new LongsPrimitiveCollectionDeserializer()), + SHORT(ShortsType, short.class, Short.class, () -> new ShortsPrimitiveCollectionDeserializer()); + + private final Class> type; + private final Class primitiveType; + private final Class objectType; + private final Supplier deserializerFactory; + + private Primitives(Class> type, Class primitiveType, Class objectType, Supplier deserializerFactory) { + this.type = type; + this.primitiveType = primitiveType; + this.objectType = objectType; + this.deserializerFactory = deserializerFactory; + } + + public Class> type() { + return (Class>) type; + } + + public Class primitiveType() { + return primitiveType; + } + + public Class objectType() { + return (Class) objectType; + } + + public BasePrimitiveCollectionDeserializer, Collection> newDeserializer() { + return deserializerFactory.get(); + } + } + + public static Optional isAssignableFromPrimitive(Class valueType) { + for (PrimitiveTypes.Primitives primitive : PrimitiveTypes.Primitives.values()) { + if (primitive.type().isAssignableFrom(valueType)) { + return Optional.of(primitive); + } + } + return Optional.absent(); + } + + /** Type of list returned by {@link Booleans#asList(boolean...)} */ + public static final Class> BooleansType; + /** Type of list returned by {@link Bytes#asList(byte...)} */ + public static final Class> BytesType; + /** Type of list returned by {@link Chars#asList(char...)} */ + public static final Class> CharsType; + /** Type of list returned by {@link Doubles#asList(double...)} */ + public static final Class> DoublesType; + /** Type of list returned by {@link Floats#asList(float...)} */ + public static final Class> FloatsType; + /** Type of list returned by {@link Ints#asList(int...)} */ + public static final Class> IntsType; + /** Type of list returned by {@link Longs#asList(long...)} */ + public static final Class> LongsType; + /** Type of list returned by {@link Shorts#asList(short...)} */ + public static final Class> ShortsType; + + static { + /* + * get the actual name of the underlying private class by creating a dummy list .. and than get its name + */ + + BooleansType = (Class>) Booleans.asList(true, false).getClass(); + BytesType = (Class>) Bytes.asList((byte) 1, (byte) 2).getClass(); + CharsType = (Class>) Chars.asList((char) 1, (char) 2).getClass(); + DoublesType = (Class>) Doubles.asList(1d, 2d).getClass(); + FloatsType = (Class>) Floats.asList((float) 1d, (float) 2d).getClass(); + IntsType = (Class>) Ints.asList(0, 1).getClass(); + LongsType = (Class>) Longs.asList(0L, 1L).getClass(); + ShortsType = (Class>) Shorts.asList((short) 0, (short) 1).getClass(); + } + + public static final TypeReference> BooleansTypeReference = typeRefOf(BooleansType); + public static final TypeReference> BytesTypeReference = typeRefOf(BytesType); + public static final TypeReference> CharsTypeReference = typeRefOf(CharsType); + public static final TypeReference> DoublesTypeReference = typeRefOf(DoublesType); + public static final TypeReference> FloatsTypeReference = typeRefOf(FloatsType); + public static final TypeReference> IntsTypeReference = typeRefOf(IntsType); + public static final TypeReference> LongsTypeReference = typeRefOf(LongsType); + public static final TypeReference> ShortsTypeReference = typeRefOf(ShortsType); + + public static final String BooleansTypeName = BooleansType.getName(); + public static final String BytesTypeName = BytesType.getName(); + public static final String CharsTypeName = CharsType.getName(); + public static final String DoublesTypeName = DoublesType.getName(); + public static final String FloatsTypeName = FloatsType.getName(); + public static final String IntsTypeName = IntsType.getName(); + public static final String LongsTypeName = LongsType.getName(); + public static final String ShortsTypeName = ShortsType.getName(); + + + private static TypeReference typeRefOf(Type type) { + return new PrimitiveTypeReference<>(type); + } + + private static class PrimitiveTypeReference extends TypeReference { + private final Type primitiveType; + + private PrimitiveTypeReference(Type primitiveType) { + this.primitiveType = primitiveType; + } + + @Override + public Type getType() { + return primitiveType; + } + } + +} diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestPrimitives.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestPrimitives.java new file mode 100644 index 00000000..450deb98 --- /dev/null +++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestPrimitives.java @@ -0,0 +1,317 @@ +package com.fasterxml.jackson.datatype.guava; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.guava.util.PrimitiveTypes; +import com.google.common.primitives.Booleans; +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Chars; +import com.google.common.primitives.Doubles; +import com.google.common.primitives.Floats; +import com.google.common.primitives.ImmutableDoubleArray; +import com.google.common.primitives.ImmutableIntArray; +import com.google.common.primitives.ImmutableLongArray; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.common.primitives.Shorts; +import com.google.common.primitives.SignedBytes; +import com.google.common.primitives.UnsignedBytes; +import com.google.common.primitives.UnsignedInteger; +import com.google.common.primitives.UnsignedInts; +import com.google.common.primitives.UnsignedLong; +import com.google.common.primitives.UnsignedLongs; + +import java.util.Collections; +import java.util.List; + +/** + * Unit tests for verifying that various primitive types + * (like {@link Booleans}, {@link Bytes},{@link Chars},{@link Doubles},{@link Floats},{@link ImmutableDoubleArray}, + * {@link ImmutableIntArray},{@link ImmutableLongArray},{@link Ints},{@link Longs},{@link Shorts}, + * {@link SignedBytes},{@link UnsignedBytes},{@link UnsignedInteger},{@link UnsignedInts},{@link UnsignedLong}, + * and {@link UnsignedLongs}) + * work as expected. + * + * @author robert@albertlr.ro + */ +public class TestPrimitives extends ModuleTestBase { + private final ObjectMapper MAPPER = mapperWithModule(); + + // For polymorphic cases need to allow bit more access + private final ObjectMapper POLY_MAPPER = builderWithModule() + .polymorphicTypeValidator(BasicPolymorphicTypeValidator + .builder() + .allowIfBaseType(Object.class) + .build() + ).build(); + + private final ObjectMapper SINGLE_MAPPER = builderWithModule() + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .build(); + + static class PolymorphicHolder { + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) + public Object value; + + public PolymorphicHolder() { + } + + public PolymorphicHolder(Object v) { + value = v; + } + } + + /********************************************************************** + * Unit tests for verifying handling in absence of module registration + /***********************************************************************/ + + /** + * Immutable types can actually be serialized as regular collections, without + * problems. + */ + public void testWithoutSerializers() throws Exception { + assertEquals("[true,false,true]", MAPPER.writeValueAsString(Booleans.asList(true, false, true))); + assertEquals("[1,2,3]", MAPPER.writeValueAsString(Bytes.asList((byte) 1, (byte) 2, (byte) 3))); + assertEquals("[\"a\",\"b\",\"c\"]", MAPPER.writeValueAsString(Chars.asList('a', 'b', 'c'))); + assertEquals("[1.5,2.5,3.5]", MAPPER.writeValueAsString(Doubles.asList(1.5, 2.5, 3.5))); + assertEquals("[1.5,2.5,3.5]", MAPPER.writeValueAsString(Floats.asList((float) 1.5, (float) 2.5, (float) 3.5))); + assertEquals("[1,2,3]", MAPPER.writeValueAsString(Ints.asList(1, 2, 3))); + assertEquals("[1,2,3]", MAPPER.writeValueAsString(Longs.asList(1L, 2L, 3L))); + assertEquals("[1,2,3]", MAPPER.writeValueAsString(Shorts.asList((short) 1, (short) 2, (short) 3))); + } + + /** + * Deserialization will fail, however. + */ + public void testWithoutDeserializers() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + try { + mapper.readValue("[true,false,true]", PrimitiveTypes.BooleansTypeReference); + fail("Expected failure for missing deserializer"); + } catch (JacksonException e) { + verifyException(e, PrimitiveTypes.BooleansTypeName); + } + + try { + mapper.readValue("[1,2,3]", PrimitiveTypes.IntsTypeReference); + fail("Expected failure for missing deserializer"); + } catch (JacksonException e) { + verifyException(e, PrimitiveTypes.IntsTypeName); + } + + try { + mapper.readValue("[1,2,3]", PrimitiveTypes.LongsTypeReference); + fail("Expected failure for missing deserializer"); + } catch (JacksonException e) { + verifyException(e, PrimitiveTypes.LongsTypeName); + } + + } + + /********************************************************************** + * Basic tests for actual registered module + /***********************************************************************/ + + public void testBooleans() throws Exception { + List list = MAPPER.readValue("[true,false,true]", PrimitiveTypes.BooleansTypeReference); + assertEquals(3, list.size()); + assertEquals(Boolean.TRUE, list.get(0)); + assertEquals(Boolean.FALSE, list.get(1)); + assertEquals(Boolean.TRUE, list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.BooleansTypeName)); + } + + public void testBooleansFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("true", PrimitiveTypes.BooleansTypeReference); + assertEquals(1, list.size()); + assertEquals(Boolean.TRUE, list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.BooleansTypeName)); + } + + public void testBytes() throws Exception { + List list = MAPPER.readValue("[1,2,3]", PrimitiveTypes.BytesTypeReference); + assertEquals(3, list.size()); + assertEquals(Byte.valueOf((byte) 1), list.get(0)); + assertEquals(Byte.valueOf((byte) 2), list.get(1)); + assertEquals(Byte.valueOf((byte) 3), list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.BytesTypeName)); + } + + public void testBytesFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("1", PrimitiveTypes.BytesTypeReference); + assertEquals(1, list.size()); + assertEquals(Byte.valueOf((byte) 1), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.BytesTypeName)); + } + + public void testChars() throws Exception { + List list = MAPPER.readValue("[\"\\u0001\",\"\\u0002\",\"\\u0003\",\"a\",\"b\",\"c\",\"D\",\"E\"]", PrimitiveTypes.CharsTypeReference); + assertEquals(8, list.size()); + assertEquals(Character.valueOf((char) 1), list.get(0)); + assertEquals(Character.valueOf((char) 2), list.get(1)); + assertEquals(Character.valueOf((char) 3), list.get(2)); + assertEquals(Character.valueOf('a'), list.get(3)); + assertEquals(Character.valueOf('b'), list.get(4)); + assertEquals(Character.valueOf('c'), list.get(5)); + assertEquals(Character.valueOf('D'), list.get(6)); + assertEquals(Character.valueOf('E'), list.get(7)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.CharsTypeName)); + } + + public void testCharsFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("\"a\"", PrimitiveTypes.CharsType); + assertEquals(1, list.size()); + assertEquals(Character.valueOf('a'), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.CharsTypeName)); + } + + public void testFloats() throws Exception { + List list = MAPPER.readValue("[1.5,2.5,3.5]", PrimitiveTypes.FloatsTypeReference); + assertEquals(3, list.size()); + assertEquals(Float.valueOf((float) 1.5), list.get(0)); + assertEquals(Float.valueOf((float) 2.5), list.get(1)); + assertEquals(Float.valueOf((float) 3.5), list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.FloatsTypeName)); + } + + public void testFloatsFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("1", PrimitiveTypes.FloatsType); + assertEquals(1, list.size()); + assertEquals(Float.valueOf(1), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.FloatsTypeName)); + } + + public void testDoubles() throws Exception { + List list = MAPPER.readValue("[1.5,2.5,3.5]", PrimitiveTypes.DoublesTypeReference); + assertEquals(3, list.size()); + assertEquals(Double.valueOf(1.5), list.get(0)); + assertEquals(Double.valueOf(2.5), list.get(1)); + assertEquals(Double.valueOf(3.5), list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.DoublesTypeName)); + } + + public void testDoublesFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("1", PrimitiveTypes.DoublesType); + assertEquals(1, list.size()); + assertEquals(Double.valueOf(1d), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.DoublesTypeName)); + } + + public void testInts() throws Exception { + List list = MAPPER.readValue("[1,2,3]", PrimitiveTypes.IntsTypeReference); + assertEquals(3, list.size()); + assertEquals(Integer.valueOf(1), list.get(0)); + assertEquals(Integer.valueOf(2), list.get(1)); + assertEquals(Integer.valueOf(3), list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.IntsTypeName)); + } + + public void testIntsFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("1", PrimitiveTypes.IntsTypeReference); + assertEquals(1, list.size()); + assertEquals(Integer.valueOf(1), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.IntsTypeName)); + } + + public void testLongs() throws Exception { + List list = MAPPER.readValue("[1,2,3]", PrimitiveTypes.LongsTypeReference); + assertEquals(3, list.size()); + assertEquals(Long.valueOf(1), list.get(0)); + assertEquals(Long.valueOf(2), list.get(1)); + assertEquals(Long.valueOf(3), list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.LongsTypeName)); + } + + public void testLongsFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("1", PrimitiveTypes.LongsTypeReference); + assertEquals(1, list.size()); + assertEquals(Long.valueOf(1), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.LongsTypeName)); + } + + public void testShorts() throws Exception { + List list = MAPPER.readValue("[1,2,3]", PrimitiveTypes.ShortsTypeReference); + assertEquals(3, list.size()); + assertEquals(Short.valueOf((short) 1), list.get(0)); + assertEquals(Short.valueOf((short) 2), list.get(1)); + assertEquals(Short.valueOf((short) 3), list.get(2)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.ShortsTypeName)); + } + + public void testShortsFromSingle() throws Exception { + List list = SINGLE_MAPPER.readValue("1", PrimitiveTypes.ShortsTypeReference); + assertEquals(1, list.size()); + assertEquals(Short.valueOf((short) 1), list.get(0)); + assertTrue(list.getClass().getName().equals(PrimitiveTypes.ShortsTypeName)); + } + + /* + /********************************************************************** + /* Polymorphic handling + /********************************************************************** + */ + + public void testTypedInts() throws Exception { + PolymorphicHolder h; + String json; + PolymorphicHolder result; + + // First, with one entry + List ints = Ints.asList(1); + h = new PolymorphicHolder(ints); + json = POLY_MAPPER.writeValueAsString(h); + + // so far so good. and back? + result = POLY_MAPPER.readValue(json, PolymorphicHolder.class); + assertNotNull(result.value); + if (!(PrimitiveTypes.IntsType.isInstance(result.value))) { + fail("Expected " + PrimitiveTypes.IntsTypeName + ", got " + result.value.getClass()); + } + assertEquals(1, ((List) result.value).size()); + + // and then an empty version: + List emptyList = Ints.asList(new int[0]); + h = new PolymorphicHolder(emptyList); + json = POLY_MAPPER.writeValueAsString(h); + result = POLY_MAPPER.readValue(json, PolymorphicHolder.class); + assertNotNull(result.value); + if (!(Collections.emptyList().getClass().isInstance(result.value))) { + fail("Expected " + Collections.emptyList().getClass().getName() + ", got " + result.value.getClass()); + } + assertEquals(0, ((List) result.value).size()); + } + + public void testTypedLongs() throws Exception { + PolymorphicHolder h; + String json; + PolymorphicHolder result; + + // First, with one entry + List longs = Longs.asList(1L); + h = new PolymorphicHolder(longs); + json = POLY_MAPPER.writeValueAsString(h); + + // so far so good. and back? + result = POLY_MAPPER.readValue(json, PolymorphicHolder.class); + assertNotNull(result.value); + if (!(PrimitiveTypes.LongsType.isInstance(result.value))) { + fail("Expected " + PrimitiveTypes.LongsTypeName + ", got " + result.value.getClass()); + } + assertEquals(1, ((List) result.value).size()); + + // and then an empty version: + List emptyList = Longs.asList(new long[0]); + h = new PolymorphicHolder(emptyList); + json = POLY_MAPPER.writeValueAsString(h); + result = POLY_MAPPER.readValue(json, PolymorphicHolder.class); + assertNotNull(result.value); + if (!(Collections.emptyList().getClass().isInstance(result.value))) { + fail("Expected " + Collections.emptyList().getClass().getName() + ", got " + result.value.getClass()); + } + assertEquals(0, ((List) result.value).size()); + } + +}