From a471f1eed1e555e95b3d9bcda31ce0277e35a14a Mon Sep 17 00:00:00 2001 From: Ajit Thakor <49403056+athakor@users.noreply.github.com> Date: Thu, 7 May 2020 04:12:10 +0530 Subject: [PATCH] feat: add support for BigDecimal to CustomClassMapper (#196) --- .../cloud/firestore/CustomClassMapper.java | 25 +++++- .../firestore/DocumentReferenceTest.java | 6 +- .../google/cloud/firestore/MapperTest.java | 85 ++++++++++++++++++- 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java index a3c8784b7..6c685767e 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CustomClassMapper.java @@ -35,6 +35,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -117,11 +118,13 @@ private static Object serialize(T o, ErrorPath path) { } else if (o instanceof Number) { if (o instanceof Long || o instanceof Integer || o instanceof Double || o instanceof Float) { return o; + } else if (o instanceof BigDecimal) { + return String.valueOf(o); } else { throw serializeError( path, String.format( - "Numbers of type %s are not supported, please use an int, long, float or double", + "Numbers of type %s are not supported, please use an int, long, float, double or BigDecimal", o.getClass().getSimpleName())); } } else if (o instanceof String) { @@ -324,6 +327,8 @@ private static T deserializeToPrimitive( return (T) convertDouble(o, context); } else if (Long.class.isAssignableFrom(clazz) || long.class.isAssignableFrom(clazz)) { return (T) convertLong(o, context); + } else if (BigDecimal.class.isAssignableFrom(clazz)) { + return (T) convertBigDecimal(o, context); } else if (Float.class.isAssignableFrom(clazz) || float.class.isAssignableFrom(clazz)) { return (T) (Float) convertDouble(o, context).floatValue(); } else { @@ -462,6 +467,24 @@ private static Double convertDouble(Object o, DeserializeContext context) { } } + private static BigDecimal convertBigDecimal(Object o, DeserializeContext context) { + if (o instanceof Integer) { + return BigDecimal.valueOf(((Integer) o).intValue()); + } else if (o instanceof Long) { + return BigDecimal.valueOf(((Long) o).longValue()); + } else if (o instanceof Double) { + return BigDecimal.valueOf(((Double) o).doubleValue()).abs(); + } else if (o instanceof BigDecimal) { + return (BigDecimal) o; + } else if (o instanceof String) { + return new BigDecimal((String) o); + } else { + throw deserializeError( + context.errorPath, + "Failed to convert a value of type " + o.getClass().getName() + " to BigDecimal"); + } + } + private static Boolean convertBoolean(Object o, DeserializeContext context) { if (o instanceof Boolean) { return (Boolean) o; diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java index 7110c270f..151569776 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java @@ -180,19 +180,19 @@ public void doesNotSerializeAdvancedNumberTypes() { pojo.bigIntegerValue = new BigInteger("0"); expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type BigInteger are not supported, please use an int, long, float or double (found in field 'bigIntegerValue')"); + "Could not serialize object. Numbers of type BigInteger are not supported, please use an int, long, float, double or BigDecimal (found in field 'bigIntegerValue')"); pojo = new InvalidPOJO(); pojo.byteValue = 0; expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type Byte are not supported, please use an int, long, float or double (found in field 'byteValue')"); + "Could not serialize object. Numbers of type Byte are not supported, please use an int, long, float, double or BigDecimal (found in field 'byteValue')"); pojo = new InvalidPOJO(); pojo.shortValue = 0; expectedErrorMessages.put( pojo, - "Could not serialize object. Numbers of type Short are not supported, please use an int, long, float or double (found in field 'shortValue')"); + "Could not serialize object. Numbers of type Short are not supported, please use an int, long, float, double or BigDecimal (found in field 'shortValue')"); for (Map.Entry testCase : expectedErrorMessages.entrySet()) { try { diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java index 468a7f57a..a64eb466c 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/MapperTest.java @@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableList; import com.google.firestore.v1.DatabaseRootName; import java.io.Serializable; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -39,6 +40,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; @@ -74,6 +76,31 @@ public double getValue() { } } + private static class BigDecimalBean { + private BigDecimal value; + + public BigDecimal getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BigDecimalBean bean = (BigDecimalBean) o; + return Objects.equals(value, bean.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + private static class FloatBean { private float value; @@ -1049,6 +1076,41 @@ public void primitiveDeserializeDouble() { } } + @Test + public void primitiveDeserializeBigDecimal() { + BigDecimalBean beanBigdecimal = deserialize("{'value': 123}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(123), beanBigdecimal.value); + + beanBigdecimal = deserialize("{'value': '123'}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(123), beanBigdecimal.value); + + // Int + BigDecimalBean beanInt = deserialize("{'value': 1}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1), beanInt.value); + + // Long + BigDecimalBean beanLong = deserialize("{'value': 1234567890123}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1234567890123L), beanLong.value); + + // Double + BigDecimalBean beanDouble = deserialize("{'value': 1.1}", BigDecimalBean.class); + assertEquals(BigDecimal.valueOf(1.1), beanDouble.value); + + // Boolean + try { + deserialize("{'value': true}", BigDecimalBean.class); + fail("Should throw"); + } catch (RuntimeException e) { // ignore + } + + // String + try { + deserialize("{'value': 'foo'}", BigDecimalBean.class); + fail("Should throw"); + } catch (RuntimeException e) { // ignore + } + } + @Test public void primitiveDeserializeFloat() { FloatBean beanFloat = deserialize("{'value': 1.1}", FloatBean.class); @@ -1513,6 +1575,23 @@ public void serializeLongBean() { assertJson("{'value': 1234567890123}", serialize(bean)); } + @Test + public void serializeBigDecimalBean() { + BigDecimalBean bean = new BigDecimalBean(); + bean.value = BigDecimal.valueOf(1.1); + assertEquals(mapAnyType("value", "1.1"), serialize(bean)); + } + + @Test + public void bigDecimalRoundTrip() { + BigDecimal doubleMaxPlusOne = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.ONE); + BigDecimalBean a = new BigDecimalBean(); + a.value = doubleMaxPlusOne; + Map serialized = (Map) serialize(a); + BigDecimalBean b = convertToCustomClass(serialized, BigDecimalBean.class); + assertEquals(a, b); + } + @Test public void serializeBooleanBean() { BooleanBean bean = new BooleanBean(); @@ -1827,7 +1906,7 @@ public void shortsCantBeSerialized() { final ShortBean bean = new ShortBean(); bean.value = 1; assertExceptionContains( - "Numbers of type Short are not supported, please use an int, long, float or double (found in field 'value')", + "Numbers of type Short are not supported, please use an int, long, float, double or BigDecimal (found in field 'value')", new Runnable() { @Override public void run() { @@ -1841,7 +1920,7 @@ public void bytesCantBeSerialized() { final ByteBean bean = new ByteBean(); bean.value = 1; assertExceptionContains( - "Numbers of type Byte are not supported, please use an int, long, float or double (found in field 'value')", + "Numbers of type Byte are not supported, please use an int, long, float, double or BigDecimal (found in field 'value')", new Runnable() { @Override public void run() { @@ -2478,7 +2557,7 @@ public void serializationFailureIncludesPath() { } catch (RuntimeException e) { assertEquals( "Could not serialize object. Numbers of type Short are not supported, please use an int, " - + "long, float or double (found in field 'value.inner.value.short')", + + "long, float, double or BigDecimal (found in field 'value.inner.value.short')", e.getMessage()); } }