Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced BigDecimalConverter #4557

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.fasterxml.jackson.annotation.JsonCreator;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Map;
Expand Down Expand Up @@ -49,6 +50,13 @@ public enum DataType {
*/
DOUBLE("double"),

/**
* Type of <i>BigDecimal</i>. No precision loss possible type. Compatible with the Java <b>BigDecimal</b> primitive data type.
*
* @since 2.8
*/
BIG_DECIMAL("big_decimal"),

/**
* Type of <i>map</i>. Compatible with the Java <b>map</b> primitive data type.
*
Expand Down Expand Up @@ -96,20 +104,22 @@ public static boolean isSameType(final Object object, final String option) {
if (type == null)
throw new IllegalArgumentException("Unknown DataType");
switch (type) {
case MAP:
return (object instanceof Map);
case ARRAY:
return (object instanceof ArrayList || object.getClass().isArray());
case DOUBLE:
return (object instanceof Double);
case BOOLEAN:
return (object instanceof Boolean);
case INTEGER:
return (object instanceof Integer);
case LONG:
return (object instanceof Long);
default: // STRING
return (object instanceof String);
case MAP:
return (object instanceof Map);
case ARRAY:
return (object instanceof ArrayList || object.getClass().isArray());
case DOUBLE:
return (object instanceof Double);
case BOOLEAN:
return (object instanceof Boolean);
case INTEGER:
return (object instanceof Integer);
case LONG:
return (object instanceof Long);
case BIG_DECIMAL:
return (object instanceof BigDecimal);
default: // STRING
return (object instanceof String);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.cfg.JsonNodeFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.TypeFactory;
Expand Down Expand Up @@ -61,11 +63,14 @@ public class JacksonEvent implements Event {

private static final String SEPARATOR = "/";

private static final ObjectMapper mapper = new ObjectMapper()
private static final ObjectMapper mapper = JsonMapper.builder()
.disable(JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES)
.build()
.registerModule(new JavaTimeModule())
.registerModule(new Jdk8Module()); // required for using Optional with Jackson. Ref: https://github.com/FasterXML/jackson-modules-java8

private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {

private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<>() {
};

private final EventMetadata eventMetadata;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.dataprepper.typeconverter;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
* Converter class for BigDecimal data type. By default, it applies zero scaling keeping the original value as it is.
* If required, the scale can be set using the setScale method.
*/
public class BigDecimalConverter implements TypeConverter<BigDecimal> {

public BigDecimal convert(Object source, ConverterArguments arguments) throws IllegalArgumentException {
return this.convert(source, arguments.getScale());
}

public BigDecimal convert(Object source, int scale) throws IllegalArgumentException {
BigDecimal result = null;
if (source instanceof String) {
result = new BigDecimal((String)source);
}
else if (source instanceof Float) {
result = BigDecimal.valueOf((Float)source);
}
else if (source instanceof Double) {
result = BigDecimal.valueOf((Double)source);
}
else if (source instanceof Boolean) {
result = ((Boolean)source) ? BigDecimal.valueOf(1L) : BigDecimal.valueOf(0L);
}
else if (source instanceof Integer) {
result = BigDecimal.valueOf((Integer)source);
}
else if (source instanceof Long) {
result = BigDecimal.valueOf((Long)source);
}
else if (source instanceof BigDecimal) {
result = ((BigDecimal)source);
}

if(result!=null) {
if(scale!=0) {
result = result.setScale(scale, RoundingMode.HALF_EVEN);
}
return result;
}
throw new IllegalArgumentException("Unsupported type conversion. From Source class: " + source.getClass() + " to BigDecimal");
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package org.opensearch.dataprepper.typeconverter;

public class BooleanConverter implements TypeConverter<Boolean> {
public Boolean convert(Object source) throws IllegalArgumentException {
public Boolean convert(Object source, ConverterArguments arguments) throws IllegalArgumentException {
if (source instanceof String) {
return Boolean.parseBoolean((String)source);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.opensearch.dataprepper.typeconverter;

/**
* Interface for arguments passed to the {@link TypeConverter}
*
* @since 1.2
*/
public interface ConverterArguments {
int getScale();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package org.opensearch.dataprepper.typeconverter;

public class DoubleConverter implements TypeConverter<Double> {
public Double convert(Object source) throws IllegalArgumentException {
public Double convert(Object source, ConverterArguments arguments) throws IllegalArgumentException {
if (source instanceof String) {
return Double.parseDouble((String)source);
}
Expand All @@ -17,7 +17,7 @@ public Double convert(Object source) throws IllegalArgumentException {
return (((Number)source).doubleValue());
}
if (source instanceof Boolean) {
return (double)(((Boolean)source) ? 1.0 : 0.0);
return ((Boolean)source) ? 1.0 : 0.0;
}
throw new IllegalArgumentException("Unsupported type conversion. Source class: " + source.getClass());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import java.math.BigDecimal;
public class IntegerConverter implements TypeConverter<Integer> {
public Integer convert(Object source) throws IllegalArgumentException {
public Integer convert(Object source, ConverterArguments arguments) throws IllegalArgumentException {
if (source instanceof String) {
return Integer.parseInt((String)source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import java.math.BigDecimal;
public class LongConverter implements TypeConverter<Long> {
public Long convert(Object source) throws IllegalArgumentException {
public Long convert(Object source, ConverterArguments arguments) throws IllegalArgumentException {
if (source instanceof String) {
return Long.parseLong((String)source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package org.opensearch.dataprepper.typeconverter;

public class StringConverter implements TypeConverter<String> {
public String convert(Object source) throws IllegalArgumentException {
public String convert(Object source, ConverterArguments arguments) throws IllegalArgumentException {
if (source instanceof Number || source instanceof Boolean || source instanceof String) {
return source.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
package org.opensearch.dataprepper.typeconverter;

public interface TypeConverter<T> {
T convert(Object source);
T convert(Object source, ConverterArguments arguments);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.math.BigDecimal;
import java.util.Map;
import java.util.stream.Stream;

Expand All @@ -37,12 +38,13 @@ void test_isSameType(Object object, String type, boolean expectedResult) {
}

private static Stream<Arguments> getSameTypeTestData() {
int testArray[] = {1,2};
int[] testArray = {1,2};
return Stream.of(
Arguments.of(2, "integer", true),
Arguments.of("testString", "string", true),
Arguments.of(2L, "long", true),
Arguments.of(2.0, "double", true),
Arguments.of(BigDecimal.valueOf(2.34567), "big_decimal", true),
Arguments.of(true, "boolean", true),
Arguments.of(Map.of("k","v"), "map", true),
Arguments.of(testArray, "array", true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.opensearch.dataprepper.expression.ExpressionEvaluator;
import org.opensearch.dataprepper.model.event.exceptions.EventKeyNotFoundException;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -22,6 +25,7 @@
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.containsStringIgnoringCase;
import static org.hamcrest.CoreMatchers.equalTo;
Expand Down Expand Up @@ -882,4 +886,24 @@ private static Map<String, Object> createComplexDataMap() {
return dataObject;
}

@ParameterizedTest
@MethodSource("getBigDecimalPutTestData")
void testPutAndGet_withBigDecimal(final String value) {
final String key = "bigDecimalKey";
event.put(key, new BigDecimal(value));
final Object result = event.get(key, Object.class);
assertThat(result, is(notNullValue()));
assertThat(result.toString(), is(equalTo(value)));
}

private static Stream<Arguments> getBigDecimalPutTestData() {
return Stream.of(
Arguments.of("702062202420"),
Arguments.of("1.23345E+9"),
Arguments.of("1.2345E+60"),
Arguments.of("1.2345E+6"),
Arguments.of("1.000")
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.dataprepper.typeconverter;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class BigDecimalConverterTests {
@Test
void testStringToBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
final String stringConstant = "12345678912.12345";
assertThat(converter.convert(stringConstant), equalTo(new BigDecimal(stringConstant)));
}

@Test
void testIntegerToBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
final int intConstant = 12345;
assertThat(converter.convert(intConstant), equalTo(BigDecimal.valueOf(intConstant)));
}

@Test
void testLongToBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
final long longConstant = 123456789012L;
assertThat(converter.convert(longConstant).longValue(), equalTo(longConstant));
}

@Test
void testBooleanToBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
final Boolean boolFalseConstant = false;
assertThat(converter.convert(boolFalseConstant), equalTo(BigDecimal.valueOf(0)));
final Boolean boolTrueConstant = true;
assertThat(converter.convert(boolTrueConstant), equalTo(BigDecimal.valueOf(1)));
}

@Test
void testFloatToBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
final float fval = 12345.6789f;
assertThat(converter.convert(fval).floatValue(), equalTo(fval));
}

@Test
void testBigDecimalToBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
BigDecimal bigDecimal = new BigDecimal("12345.6789");
assertThat(converter.convert(bigDecimal), equalTo(bigDecimal));
}

@ParameterizedTest
@MethodSource("decimalToBigDecimalValueProvider")
void testDoubleToBigDecimalConversion(BigDecimal expectedBigDecimal, double actualValue, int scale) {
BigDecimalConverter converter = new BigDecimalConverter();
if(scale!=0) {
expectedBigDecimal = expectedBigDecimal.setScale(scale, RoundingMode.HALF_EVEN);
}
assertThat(converter.convert(actualValue, scale), equalTo(expectedBigDecimal));
}

private static Stream<Arguments> decimalToBigDecimalValueProvider() {
return Stream.of(
Arguments.of(new BigDecimal ("0.0"), 0, 1),
Copy link
Member

@graytaylor0 graytaylor0 May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add another test case that covers the conversion described in the issue here (#3840) where we can convert 1.70206220242E+12 to 1702062202420

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added additional scenario for this case

Arguments.of(new BigDecimal ("0.0"), 0.0, 1),
Arguments.of(new BigDecimal ("0.00000000000000000000000"), 0.00000000000000000000000, 1),
Arguments.of(BigDecimal.ZERO, BigDecimal.ZERO.doubleValue(), 1),
Arguments.of(new BigDecimal ("1"), (double)1, 1),
Arguments.of(new BigDecimal ("1703908514.045833"), 1703908514.045833, 6),
Arguments.of(new BigDecimal ("1.00000000000000000000000"), 1.00000000000000000000000, 1),
Arguments.of(new BigDecimal ("-12345678912.12345"), -12345678912.12345, 1),
Arguments.of(BigDecimal.ONE, BigDecimal.ONE.doubleValue(), 1),
Arguments.of(new BigDecimal("1.7976931348623157E+308"), 1.7976931348623157E+308, 0),
Arguments.of(new BigDecimal("1702062202420"), 1.70206220242E+12, 12),
Arguments.of(BigDecimal.valueOf(Double.MAX_VALUE), Double.MAX_VALUE, 0),
Arguments.of(BigDecimal.valueOf(Double.MIN_VALUE), Double.MIN_VALUE, 0)
);
}

@Test
void testInvalidBigDecimalConversion() {
BigDecimalConverter converter = new BigDecimalConverter();
final Map<Object, Object> map = Collections.emptyMap();
assertThrows(IllegalArgumentException.class, () -> converter.convert(map));
}
}
Loading
Loading