Repository: influxdata/influxdb-client-java (java)

Issue:
Problem Mapping Long fields
When It is mapped a Long (Not primitive) field on model class and we use pivot function to return the values for each field on model, a Cast exception is thrown during the execution.

It says is not possible cast Long to Double.
When we use long (primitive) it doesn't throw error, however it returns zero (not behavior expected).

__Steps to reproduce__:

1. Create a model with 2 fields:

ModelInflux.java
```
//use lombok

@Getter
@Setter
@Measurement(name="model2")
@ToString
public class ModelInflux {

   @Column
  private Long field1;
  @Column
  private Double field2;

  @Column(timestamp = true, name = "time")
  private Instant timestamp;

}

```
2. Save some data on InfluxDB
Main.java
```
  public static void save(ModelInflux object, InfluxDBClientReactive client) {
    WriteReactiveApi writeApi = client.getWriteReactiveApi();

    var publisher = writeApi.writeMeasurement(WritePrecision.NS, object);
    Disposable subscriber =
        Flowable.fromPublisher(publisher)
            .subscribe(success -> log.info("Successfully written ModelInflux measurement"));
    subscriber.dispose();
  }
```

3. Create a query with pivot function:
```
private static void runModelInflux(String host, String org, String token, String bucket)
      throws InterruptedException {

    String query = "from(bucket: \"test2\")"
        + "  |> range(start: -1h, stop: now())"
        + "  |> filter(fn: (r) => r[\"_measurement\"] == \"model2\")"
        + "  |> aggregateWindow(every: 10s, fn: mean, createEmpty: false)"
        + "  |> yield(name: \"mean\")"
        + "  |> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")";

    InfluxDBClientReactive client = InfluxDBClientReactiveFactory
        .create(host, token.toCharArray(), org, bucket);

    ModelInflux modelInflux = new ModelInflux();
    modelInflux.setField1((long)(Math.random() * 100));
    modelInflux.setField2((Math.random() * 100));

    save(modelInflux, client);

    Thread.sleep(1000);

    Instant end = Instant.now();
    Instant start = Instant.now().minus(1, ChronoUnit.HOURS);

    execute(query, client).doOnEach( e -> {
      if (e.get() != null) {
        log.info(e.get().toString());
      }
    }).doOnError(e -> {
      log.error(e.getMessage());
    }).subscribe();

    // Giving a time to process the result.
    Thread.sleep(10000);
  }
```
Main.java

```
  public static void main(String[] args) throws InterruptedException {
    String host = Main.HOST;
    String org = Main.ORG;
    String token= Main.TOKEN;
    String bucket = Main.BUCKET;

    runModelInflux2(host, org, token, "test2");
  }
```


__Expected Behavior__:

Return the model without error and value available.

__Actual Behavior__:
Error:
Caused by: com.influxdb.exceptions.InfluxException: Can not set java.lang.Long field com.autonomic.test.domain.ModelInflux2.field1 to java.lang.Double

__Specifications:__
 - Client Version: 6.4.0
 - InfluxDB Version: 2.0.9
 - JDK Version: Corretto 11.0.15.9.1
 - Platform: Mac OS M1

PS.: I see this code is converting only to primitives. (Maybe include more options to conversion?)
https://github.com/influxdata/influxdb-client-java/blob/f9f6aa0429977525ffaef4f745ab1f7958623cb1/client-core/src/main/java/com/influxdb/query/internal/FluxResultMapper.java#L132-L144



Pull request:
fix: mapping `Number` types to POJO query output
Closes #410

## Proposed Changes

Fixed mapping aggregated results (changed type) from InfluxDB.

## Checklist

<!-- Checkboxes below this note can be erased if not applicable to your Pull Request. -->

- [x] CHANGELOG.md updated
- [x] Rebased/mergeable
- [x] A test has been added if appropriate
- [x] `mvn test` completes successfully
- [x] Commit messages are [conventional](https://www.conventionalcommits.org/en/v1.0.0/)
- [x] Sign [CLA](https://www.influxdata.com/legal/cla/) (if not already signed)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e165d6066f..94093ea547 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@
 ### Features
 1. [#419](https://github.com/influxdata/influxdb-client-java/pull/419): Add possibility to get time from `Point` data structure
 
+### Bug Fixes
+1. [#414](https://github.com/influxdata/influxdb-client-java/pull/414): Mapping `Number` types to POJO query output
+
 ### Documentation
 1. [#406](https://github.com/influxdata/influxdb-client-java/pull/406): Fix compatibility of the `doclint` between JDK 8 and JDK 18
 
diff --git a/client-core/src/main/java/com/influxdb/query/internal/FluxResultMapper.java b/client-core/src/main/java/com/influxdb/query/internal/FluxResultMapper.java
index e75a63f8cb..d2544a2269 100644
--- a/client-core/src/main/java/com/influxdb/query/internal/FluxResultMapper.java
+++ b/client-core/src/main/java/com/influxdb/query/internal/FluxResultMapper.java
@@ -129,16 +129,24 @@ public class FluxResultMapper {
             }
 
             //convert primitives
-            if (double.class.isAssignableFrom(fieldType)) {
-                field.setDouble(object, toDoubleValue(value));
+            if (double.class.isAssignableFrom(fieldType) || Double.class.isAssignableFrom(fieldType)) {
+                field.set(object, toDoubleValue(value));
                 return;
             }
-            if (long.class.isAssignableFrom(fieldType)) {
-                field.setLong(object, toLongValue(value));
+            if (long.class.isAssignableFrom(fieldType) || Long.class.isAssignableFrom(fieldType)) {
+                field.set(object, toLongValue(value));
                 return;
             }
-            if (int.class.isAssignableFrom(fieldType)) {
-                field.setInt(object, toIntValue(value));
+            if (int.class.isAssignableFrom(fieldType) || Integer.class.isAssignableFrom(fieldType)) {
+                field.set(object, toIntValue(value));
+                return;
+            }
+            if (float.class.isAssignableFrom(fieldType) || Float.class.isAssignableFrom(fieldType)) {
+                field.set(object, toFloatValue(value));
+                return;
+            }
+            if (short.class.isAssignableFrom(fieldType) || Short.class.isAssignableFrom(fieldType)) {
+                field.set(object, toShortValue(value));
                 return;
             }
             if (boolean.class.isAssignableFrom(fieldType)) {
@@ -172,7 +180,7 @@ public class FluxResultMapper {
             return (double) value;
         }
 
-        return (Double) value;
+        return ((Number) value).doubleValue();
     }
 
     private long toLongValue(final Object value) {
@@ -181,7 +189,7 @@ public class FluxResultMapper {
             return (long) value;
         }
 
-        return ((Double) value).longValue();
+        return ((Number) value).longValue();
     }
 
     private int toIntValue(final Object value) {
@@ -190,7 +198,25 @@ public class FluxResultMapper {
             return (int) value;
         }
 
-        return ((Double) value).intValue();
+        return ((Number) value).intValue();
+    }
+
+    private float toFloatValue(final Object value) {
+
+        if (float.class.isAssignableFrom(value.getClass()) || Float.class.isAssignableFrom(value.getClass())) {
+            return (float) value;
+        }
+
+        return ((Number) value).floatValue();
+    }
+
+    private short toShortValue(final Object value) {
+
+        if (short.class.isAssignableFrom(value.getClass()) || Short.class.isAssignableFrom(value.getClass())) {
+            return (short) value;
+        }
+
+        return ((Number) value).shortValue();
     }
 
     private BigDecimal toBigDecimalValue(final Object value) {
@@ -210,6 +236,14 @@ public class FluxResultMapper {
             return BigDecimal.valueOf((long) value);
         }
 
+        if (float.class.isAssignableFrom(value.getClass()) || Float.class.isAssignableFrom(value.getClass())) {
+            return BigDecimal.valueOf((float) value);
+        }
+
+        if (short.class.isAssignableFrom(value.getClass()) || Short.class.isAssignableFrom(value.getClass())) {
+            return BigDecimal.valueOf((short) value);
+        }
+
         String message = String.format("Cannot cast %s [%s] to %s.",
                 value.getClass().getName(), value, BigDecimal.class);
 


Test patch:
diff --git a/client-core/src/test/java/com/influxdb/query/internal/FluxResultMapperTest.java b/client-core/src/test/java/com/influxdb/query/internal/FluxResultMapperTest.java
index 4c8ef93249..0681443bb7 100644
--- a/client-core/src/test/java/com/influxdb/query/internal/FluxResultMapperTest.java
+++ b/client-core/src/test/java/com/influxdb/query/internal/FluxResultMapperTest.java
@@ -114,6 +114,82 @@ class FluxResultMapperTest {
         Assertions.assertThat(bean.value).isEqualByComparingTo(new BigDecimal(20));
     }
 
+    @Test
+    public void numberConversion() {
+
+        FluxRecord record = new FluxRecord(0);
+
+        record.getValues().put("fieldLong", 55);
+        record.getValues().put("fieldDouble", 55);
+        record.getValues().put("fieldInt", 55);
+        record.getValues().put("fieldFloat", 55);
+        record.getValues().put("fieldShort", 55);
+        record.getValues().put("fieldBigDecimal", 55);
+        NumberFields bean = mapper.toPOJO(record, NumberFields.class);
+        Assertions.assertThat(bean.fieldLong).isEqualTo(55L);
+        Assertions.assertThat(bean.fieldDouble).isEqualTo(55.0);
+        Assertions.assertThat(bean.fieldInt).isEqualTo(55);
+        Assertions.assertThat(bean.fieldFloat).isEqualTo(55F);
+        Assertions.assertThat(bean.fieldShort).isEqualTo((short) 55);
+        Assertions.assertThat(bean.fieldBigDecimal).isEqualByComparingTo(BigDecimal.valueOf(55));
+
+        record.getValues().put("fieldLong", 55.0);
+        record.getValues().put("fieldDouble", 55.0);
+        record.getValues().put("fieldInt", 55.0);
+        record.getValues().put("fieldFloat", 55.0);
+        record.getValues().put("fieldShort", 55.0);
+        record.getValues().put("fieldBigDecimal", 55.0);
+        bean = mapper.toPOJO(record, NumberFields.class);
+        Assertions.assertThat(bean.fieldLong).isEqualTo(55L);
+        Assertions.assertThat(bean.fieldDouble).isEqualTo(55.0);
+        Assertions.assertThat(bean.fieldInt).isEqualTo(55);
+        Assertions.assertThat(bean.fieldFloat).isEqualTo(55F);
+        Assertions.assertThat(bean.fieldShort).isEqualTo((short) 55);
+        Assertions.assertThat(bean.fieldBigDecimal).isEqualByComparingTo(BigDecimal.valueOf(55));
+
+        record.getValues().put("fieldLong", 55L);
+        record.getValues().put("fieldDouble", 55L);
+        record.getValues().put("fieldInt", 55L);
+        record.getValues().put("fieldFloat", 55L);
+        record.getValues().put("fieldShort", 55L);
+        record.getValues().put("fieldBigDecimal", 55L);
+        bean = mapper.toPOJO(record, NumberFields.class);
+        Assertions.assertThat(bean.fieldLong).isEqualTo(55L);
+        Assertions.assertThat(bean.fieldDouble).isEqualTo(55.0);
+        Assertions.assertThat(bean.fieldInt).isEqualTo(55);
+        Assertions.assertThat(bean.fieldFloat).isEqualTo(55F);
+        Assertions.assertThat(bean.fieldShort).isEqualTo((short) 55);
+        Assertions.assertThat(bean.fieldBigDecimal).isEqualByComparingTo(BigDecimal.valueOf(55));
+
+        record.getValues().put("fieldLong", 55F);
+        record.getValues().put("fieldDouble", 55F);
+        record.getValues().put("fieldInt", 55F);
+        record.getValues().put("fieldFloat", 55F);
+        record.getValues().put("fieldShort", 55f);
+        record.getValues().put("fieldBigDecimal", 55f);
+        bean = mapper.toPOJO(record, NumberFields.class);
+        Assertions.assertThat(bean.fieldLong).isEqualTo(55L);
+        Assertions.assertThat(bean.fieldDouble).isEqualTo(55.0);
+        Assertions.assertThat(bean.fieldInt).isEqualTo(55);
+        Assertions.assertThat(bean.fieldFloat).isEqualTo(55F);
+        Assertions.assertThat(bean.fieldShort).isEqualTo((short) 55);
+        Assertions.assertThat(bean.fieldBigDecimal).isEqualByComparingTo(BigDecimal.valueOf(55));
+
+        record.getValues().put("fieldLong", (short) 55);
+        record.getValues().put("fieldDouble", (short) 55);
+        record.getValues().put("fieldInt", (short) 55);
+        record.getValues().put("fieldFloat", (short) 55);
+        record.getValues().put("fieldShort", (short) 55);
+        record.getValues().put("fieldBigDecimal", (short) 55);
+        bean = mapper.toPOJO(record, NumberFields.class);
+        Assertions.assertThat(bean.fieldLong).isEqualTo(55L);
+        Assertions.assertThat(bean.fieldDouble).isEqualTo(55.0);
+        Assertions.assertThat(bean.fieldInt).isEqualTo(55);
+        Assertions.assertThat(bean.fieldFloat).isEqualTo(55F);
+        Assertions.assertThat(bean.fieldShort).isEqualTo((short) 55);
+        Assertions.assertThat(bean.fieldBigDecimal).isEqualByComparingTo(BigDecimal.valueOf(55));
+    }
+
     public static class BigDecimalBean {
         @Column(name = "value1")
         BigDecimal value1;
@@ -166,4 +242,19 @@ class FluxResultMapperTest {
         @Column(name = "value")
         BigDecimal value;
     }
+
+    public static class NumberFields {
+        @Column
+        private Long fieldLong;
+        @Column
+        private Double fieldDouble;
+        @Column
+        private Integer fieldInt;
+        @Column
+        private Float fieldFloat;
+        @Column
+        private Short fieldShort;
+        @Column
+        private BigDecimal fieldBigDecimal;
+    }
 }