This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 86e0ee42e3f5 CAMEL-25703: camel-bindy - keep all decimals of a number
field without precision when marshalling (#27988)
86e0ee42e3f5 is described below
commit 86e0ee42e3f538cf758ca5f65982be2ab183aec9
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 17 14:43:08 2026 +0530
CAMEL-25703: camel-bindy - keep all decimals of a number field without
precision when marshalling (#27988)
Since CAMEL-24168 the default precision of @DataField and @KeyValuePairField
is -1, so AbstractNumberFormat no longer sets the fraction digits of its
DecimalFormat. The default pattern of DecimalFormat keeps at most three
fraction digits, so a BigDecimal, Double or Float field without precision
and pattern was marshalled rounded: 1.23456 became 1.235 and 0.000123
became 0, and a record unmarshalled and marshalled again changed.
Without a precision the formatter now keeps all fraction digits. A finite
Float with a fraction part is formatted from Float.toString, so 1.1f is
still written as 1.1 and not as its widening to double; a whole-number
Float keeps its exact digits as before. Explicit precision, pattern and
implied
decimal separator are unchanged. Upgrade note in the 4.24 guide.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../bindy/format/AbstractNumberFormat.java | 3 +
.../bindy/format/factories/FloatFormatFactory.java | 12 +-
...ndyNumberWithoutPrecisionKeepsDecimalsTest.java | 174 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 9 ++
4 files changed, 195 insertions(+), 3 deletions(-)
diff --git
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
index 742761d00ef5..01d52b36d2ee 100644
---
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
+++
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java
@@ -62,6 +62,9 @@ public abstract class AbstractNumberFormat<T extends Number>
implements Format<T
} else if (this.precision >= 0) {
this.format.setMinimumFractionDigits(this.precision);
this.format.setMaximumFractionDigits(this.precision);
+ } else {
+ // no precision: keep every decimal of the value (the default of
DecimalFormat rounds to three)
+ this.format.setMaximumFractionDigits(Integer.MAX_VALUE);
}
}
diff --git
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
index c9a589fc697a..72a55d5acff5 100644
---
a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
+++
b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/FloatFormatFactory.java
@@ -53,9 +53,15 @@ public class FloatFormatFactory extends
AbstractFormatFactory {
@Override
public String format(Float object) throws Exception {
- return !super.hasImpliedDecimalPosition()
- ? super.getFormat().format(object)
- : super.getFormat().format(object * super.getMultiplier());
+ if (super.hasImpliedDecimalPosition()) {
+ return super.getFormat().format(object *
super.getMultiplier());
+ }
+ if (super.getPrecision() < 0 && Float.isFinite(object) && object
!= Math.rint(object)) {
+ // no precision: the decimal digits of the float (1.1), not of
its widening to double (1.100000023841858);
+ // a whole number keeps its exact digits (123456792, not the
shorter 1.2345679E8 of Float.toString)
+ return super.getFormat().format(new
BigDecimal(object.toString()));
+ }
+ return super.getFormat().format(object);
}
@Override
diff --git
a/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyNumberWithoutPrecisionKeepsDecimalsTest.java
b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyNumberWithoutPrecisionKeepsDecimalsTest.java
new file mode 100644
index 000000000000..38a04eaea55a
--- /dev/null
+++
b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyNumberWithoutPrecisionKeepsDecimalsTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.camel.dataformat.bindy.csv;
+
+import java.math.BigDecimal;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
+import org.apache.camel.dataformat.bindy.annotation.DataField;
+import org.apache.camel.dataformat.bindy.annotation.FixedLengthRecord;
+import org.apache.camel.dataformat.bindy.annotation.KeyValuePairField;
+import org.apache.camel.dataformat.bindy.annotation.Message;
+import org.apache.camel.model.dataformat.BindyDataFormat;
+import org.apache.camel.model.dataformat.BindyType;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A BigDecimal, Double or Float field without a precision keeps all its
decimals when it is marshalled, so that a
+ * record unmarshalled and marshalled again is the same text. The default
formatter of the JDK keeps at most three
+ * decimals.
+ */
+public class BindyNumberWithoutPrecisionKeepsDecimalsTest extends
CamelTestSupport {
+
+ @Test
+ public void csvMarshalKeepsAllDecimals() {
+ CsvModel model = new CsvModel();
+ model.rate = new BigDecimal("1.23456");
+ model.ratio = 0.000123d;
+ model.weight = 1.1f;
+
+ String csv = template.requestBody("direct:csv-marshal", model,
String.class);
+
+ assertEquals("1.23456,0.000123,1.1\r\n", csv);
+ }
+
+ @Test
+ public void csvRecordIsTheSameAfterUnmarshalAndMarshal() {
+ String record = "0.98765,12.3456789,2.5\r\n";
+
+ CsvModel model = template.requestBody("direct:csv-unmarshal", record,
CsvModel.class);
+ assertEquals(new BigDecimal("0.98765"), model.rate);
+ assertEquals(12.3456789d, model.ratio);
+ assertEquals(2.5f, model.weight);
+
+ assertEquals(record, template.requestBody("direct:csv-marshal", model,
String.class));
+ }
+
+ @Test
+ public void wholeAndShortNumbersAreUnchanged() {
+ CsvModel model = new CsvModel();
+ model.rate = new BigDecimal("10");
+ model.ratio = 10d;
+ model.weight = 0.5f;
+
+ assertEquals("10,10,0.5\r\n",
template.requestBody("direct:csv-marshal", model, String.class));
+ }
+
+ @Test
+ public void wholeFloatKeepsItsDigits() {
+ CsvModel model = new CsvModel();
+ model.rate = BigDecimal.ONE;
+ model.ratio = 1d;
+ // a float that is a whole number above 2^24: Float.toString gives the
shorter 1.2345679E8
+ model.weight = 123456792f;
+
+ assertEquals("1,1,123456792\r\n",
template.requestBody("direct:csv-marshal", model, String.class));
+ }
+
+ @Test
+ public void fixedLengthRecordIsTheSameAfterUnmarshalAndMarshal() {
+ String record = " 1.23456 0.000123";
+
+ FixedModel model = template.requestBody("direct:fixed-unmarshal",
record, FixedModel.class);
+ assertEquals(new BigDecimal("1.23456"), model.amount);
+ assertEquals(0.000123d, model.factor);
+
+ assertEquals(record + "\r\n",
template.requestBody("direct:fixed-marshal", model, String.class));
+ }
+
+ @Test
+ public void floatWithScientificToStringIsWrittenInPlainDecimals() {
+ CsvModel model = new CsvModel();
+ model.rate = new BigDecimal("1");
+ model.ratio = 1d;
+ // Float.toString gives 1.23E-4 for this value
+ model.weight = 0.000123f;
+
+ assertEquals("1,1,0.000123\r\n",
template.requestBody("direct:csv-marshal", model, String.class));
+ }
+
+ @Test
+ public void keyValuePairRecordIsTheSameAfterUnmarshalAndMarshal() {
+ String record = "1=1.23456 2=0.000123 3=1.1";
+
+ KvpModel model = template.requestBody("direct:kvp-unmarshal", record,
KvpModel.class);
+ assertEquals(new BigDecimal("1.23456"), model.rate);
+ assertEquals(0.000123d, model.ratio);
+ assertEquals(1.1f, model.weight);
+
+ assertEquals(record, template.requestBody("direct:kvp-marshal", model,
String.class).trim());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ BindyDataFormat csv = new
BindyDataFormat().type(BindyType.Csv).classType(CsvModel.class).locale("en");
+ BindyDataFormat fixed
+ = new
BindyDataFormat().type(BindyType.Fixed).classType(FixedModel.class).locale("en");
+
+ from("direct:csv-marshal").marshal(csv);
+ from("direct:csv-unmarshal").unmarshal(csv);
+ from("direct:fixed-marshal").marshal(fixed);
+ from("direct:fixed-unmarshal").unmarshal(fixed);
+
+ BindyDataFormat kvp
+ = new
BindyDataFormat().type(BindyType.KeyValue).classType(KvpModel.class).locale("en");
+ from("direct:kvp-marshal").marshal(kvp);
+ from("direct:kvp-unmarshal").unmarshal(kvp);
+ }
+ };
+ }
+
+ @CsvRecord(separator = ",")
+ public static class CsvModel {
+ @DataField(pos = 1)
+ public BigDecimal rate;
+
+ @DataField(pos = 2)
+ public Double ratio;
+
+ @DataField(pos = 3)
+ public Float weight;
+ }
+
+ @FixedLengthRecord(length = 20)
+ public static class FixedModel {
+ @DataField(pos = 1, length = 10)
+ public BigDecimal amount;
+
+ @DataField(pos = 11, length = 10)
+ public Double factor;
+ }
+
+ @Message(pairSeparator = " ", keyValuePairSeparator = "=")
+ public static class KvpModel {
+ @KeyValuePairField(tag = 1)
+ public BigDecimal rate;
+
+ @KeyValuePairField(tag = 2)
+ public Double ratio;
+
+ @KeyValuePairField(tag = 3)
+ public Float weight;
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index f1cc998b316e..84afa10e507b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -753,6 +753,15 @@ now resolves to no value, as it already did without a
field, so its default valu
(`{{?hashicorp:secret:missing#field}}`) applies. Before, without a default
value, it failed with a
`NullPointerException`.
+=== camel-bindy - a number field without precision keeps all its decimals when
marshalled
+
+A `BigDecimal`, `Double` or `Float` field whose `@DataField` (or
`@KeyValuePairField`) has no `precision` and no
+`pattern` was marshalled with at most three decimals, rounded: `1.23456` was
written as `1.235` and `0.000123` as `0`
+(since Camel 4.22, CAMEL-24168; up to Camel 4.21 the default `precision = 0`
wrote these fields without any decimals).
+It is now written with all its decimals, so a record that is unmarshalled and
marshalled again is the same text. A
+`Double` that is the result of a calculation is written with all its digits
too (`0.1 + 0.2` as `0.30000000000000004`,
+before `0.3`). Set `precision` to get a fixed number of decimals, as before.
+
=== camel-ical - each unmarshal parses with its own calendar builder
The `ical` data format used one ical4j `CalendarBuilder` for all exchanges.
Two exchanges that were unmarshalled at