You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by jl...@apache.org on 2026/03/25 20:29:12 UTC
(johnzon) branch johnzon-1.2.x updated: fix(JOHNZON-426): BigInteger/BigDecimal string adapter flags swapped fix(JOHNZON-427): BigDecimal/BigInteger READ/WRITE not consistent
This is an automated email from the ASF dual-hosted git repository.
jlmonteiro pushed a commit to branch johnzon-1.2.x
in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/johnzon-1.2.x by this push:
new 63f36b8c fix(JOHNZON-426): BigInteger/BigDecimal string adapter flags swapped fix(JOHNZON-427): BigDecimal/BigInteger READ/WRITE not consistent
63f36b8c is described below
commit 63f36b8c67a270d9158a96111095628f82b7f8a5
Author: Jean-Louis Monteiro <jl...@tomitribe.com>
AuthorDate: Wed Mar 25 15:28:45 2026 -0500
fix(JOHNZON-426): BigInteger/BigDecimal string adapter flags swapped
fix(JOHNZON-427): BigDecimal/BigInteger READ/WRITE not consistent
Signed-off-by: Jean-Louis Monteiro <jl...@tomitribe.com>
---
.../org/apache/johnzon/mapper/MapperBuilder.java | 10 +++
.../java/org/apache/johnzon/mapper/Mappings.java | 6 ++
.../mapper/converter/BigDecimalConverter.java | 4 +-
.../johnzon/mapper/map/LazyConverterMap.java | 17 ++++-
.../org/apache/johnzon/mapper/LiteralTest.java | 9 ++-
.../apache/johnzon/mapper/MapperEnhancedTest.java | 2 +-
.../johnzon/mapper/NumberSerializationTest.java | 86 ++++++++++++++++++++++
7 files changed, 127 insertions(+), 7 deletions(-)
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index d58a4409..77dbeb44 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -529,6 +529,16 @@ public class MapperBuilder {
return this;
}
+ public MapperBuilder setUseBigIntegerStringAdapter(final boolean convertBigIntegerToString) {
+ adapters.setUseBigIntegerStringAdapter(convertBigIntegerToString);
+ return this;
+ }
+
+ public MapperBuilder setUseBigDecimalStringAdapter(final boolean convertBigDecimalToString) {
+ adapters.setUseBigDecimalStringAdapter(convertBigDecimalToString);
+ return this;
+ }
+
public MapperBuilder setUseBigDecimalForObjectNumbers(final boolean value) {
this.useBigDecimalForObjectNumbers = value;
return this;
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index 7c1776e1..74a2fd6a 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -681,6 +681,12 @@ public class Mappings {
if (Date.class.isAssignableFrom(type) && copyDate) {
converter = new DateWithCopyConverter(Adapter.class.cast(adapters.get(new AdapterKey(Date.class, String.class))));
+ } else if (type == BigDecimal.class || type == BigInteger.class) {
+ // BigDecimal/BigInteger are "primitives" in the mapper so they bypass
+ // config.findAdapter() in writeValue(). Use a direct get() to trigger
+ // lazy loading and respect useBigDecimalStringAdapter/useBigIntegerStringAdapter.
+ // this makes it symetric with READ
+ converter = adapters.get(new AdapterKey(type, String.class));
} else {
for (final Map.Entry<AdapterKey, Adapter<?, ?>> adapterEntry : adapters.entrySet()) {
if (adapterEntry.getKey().getFrom() == adapterEntry.getKey().getTo()) { // String -> String
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/BigDecimalConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/BigDecimalConverter.java
index b5864e09..891f60b1 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/BigDecimalConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/BigDecimalConverter.java
@@ -25,7 +25,9 @@ import java.math.BigDecimal;
public class BigDecimalConverter implements Converter<BigDecimal> {
@Override
public String toString(final BigDecimal instance) {
- return instance.toString();
+ // when using the converter, user expects the decimal notation
+ // otherwise, JsonNumber will give the E (scientific) notation
+ return instance.toPlainString();
}
@Override
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java
index 943a1f1b..823065b7 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java
@@ -85,6 +85,11 @@ public class LazyConverterMap extends ConcurrentHashMap<AdapterKey, Adapter<?, ?
private boolean useShortISO8601Format = true;
private DateTimeFormatter dateTimeFormatter;
+ // I-JSON (RFC 7493 Section 2.2): BigX exceed IEEE 754 double, string is safer by default.
+ // Set -Djohnzon.use-ijson-big-number-stringadapter.disabled=true for strict JSON-B 3.0 / TCK compliance.
+ private static final boolean IJSON_BIG_NUMBER_DEFAULT = !Boolean.getBoolean("johnzon.use-ijson-big-number-stringadapter.disabled");
+ private boolean useBigIntegerStringAdapter = IJSON_BIG_NUMBER_DEFAULT;
+ private boolean useBigDecimalStringAdapter = IJSON_BIG_NUMBER_DEFAULT;
public void setUseShortISO8601Format(final boolean useShortISO8601Format) {
this.useShortISO8601Format = useShortISO8601Format;
@@ -94,6 +99,14 @@ public class LazyConverterMap extends ConcurrentHashMap<AdapterKey, Adapter<?, ?
this.dateTimeFormatter = dateTimeFormatter;
}
+ public void setUseBigDecimalStringAdapter(final boolean useBigDecimalStringAdapter) {
+ this.useBigDecimalStringAdapter = useBigDecimalStringAdapter;
+ }
+
+ public void setUseBigIntegerStringAdapter(final boolean useBigIntegerStringAdapter) {
+ this.useBigIntegerStringAdapter = useBigIntegerStringAdapter;
+ }
+
@Override
public Adapter<?, ?> get(final Object key) {
final Adapter<?, ?> found = super.get(key);
@@ -153,10 +166,10 @@ public class LazyConverterMap extends ConcurrentHashMap<AdapterKey, Adapter<?, ?
if (from == String.class) {
return add(key, new ConverterAdapter<>(new StringConverter(), String.class));
}
- if (from == BigDecimal.class) {
+ if (from == BigDecimal.class && useBigDecimalStringAdapter) {
return add(key, new ConverterAdapter<>(new BigDecimalConverter(), BigDecimal.class));
}
- if (from == BigInteger.class) {
+ if (from == BigInteger.class && useBigIntegerStringAdapter) {
return add(key, new ConverterAdapter<>(new BigIntegerConverter(), BigInteger.class));
}
if (from == Locale.class) {
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/LiteralTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/LiteralTest.java
index 830779ee..cce805d0 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/LiteralTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/LiteralTest.java
@@ -55,10 +55,13 @@ public class LiteralTest {
return expectedJson.indexOf(o1) - expectedJson.indexOf(o2);
}
};
- new MapperBuilder().setAttributeOrder(attributeOrder).build().writeObject(nc, sw);
+ new MapperBuilder().setAttributeOrder(attributeOrder)
+ .setUseBigDecimalStringAdapter(false).setUseBigIntegerStringAdapter(false)
+ .build().writeObject(nc, sw);
assertEquals(expectedJson, sw.toString());
- final NumberClass read = new MapperBuilder().setAttributeOrder(attributeOrder).build()
- .readObject(new StringReader(sw.toString()), NumberClass.class);
+ final NumberClass read = new MapperBuilder().setAttributeOrder(attributeOrder)
+ .setUseBigDecimalStringAdapter(false).setUseBigIntegerStringAdapter(false)
+ .build().readObject(new StringReader(sw.toString()), NumberClass.class);
assertEquals(nc, read);
}
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperEnhancedTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperEnhancedTest.java
index 8073136a..30384ab7 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperEnhancedTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperEnhancedTest.java
@@ -137,7 +137,7 @@ public class MapperEnhancedTest {
public int compare(String o1, String o2) {
return json.indexOf(o1) - json.indexOf(o2);
}
- }).build().writeObject(tc2, sw);
+ }).setUseBigDecimalStringAdapter(false).build().writeObject(tc2, sw);
assertEquals(json, sw.toString());
}
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/NumberSerializationTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/NumberSerializationTest.java
index 17590188..e18fa564 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/NumberSerializationTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/NumberSerializationTest.java
@@ -19,10 +19,16 @@
package org.apache.johnzon.mapper;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
+import java.math.BigInteger;
+import org.apache.johnzon.mapper.converter.BigDecimalConverter;
+import org.apache.johnzon.mapper.internal.AdapterKey;
+import org.apache.johnzon.mapper.map.LazyConverterMap;
import org.junit.Test;
public class NumberSerializationTest {
@@ -46,6 +52,82 @@ public class NumberSerializationTest {
mapper.close();
}
+ /**
+ * Bug: BigDecimalConverter used toString() which produces scientific notation
+ * (e.g. "7.33915E-7"). Should use toPlainString() to produce "0.000000733915".
+ */
+ @Test
+ public void bigDecimalConverterUsesPlainNotation() {
+ final BigDecimalConverter converter = new BigDecimalConverter();
+ final BigDecimal smallValue = new BigDecimal("0.000000733915");
+ final String result = converter.toString(smallValue);
+ assertEquals("BigDecimalConverter should use plain notation, not scientific",
+ "0.000000733915", result);
+ }
+
+ /**
+ * Bug fix: useBigDecimalStringAdapter and useBigIntegerStringAdapter flags
+ * were swapped in LazyConverterMap. Each flag must control its own type.
+ * Both default to true (string) for I-JSON (RFC 7493) interoperability.
+ */
+ @Test
+ public void bigDecimalStringAdapterFlagControlsBigDecimal() {
+ // Default: BigDecimal adapter is ON (useBigDecimalStringAdapter=true)
+ final LazyConverterMap defaultAdapters = new LazyConverterMap();
+ assertNotNull("BigDecimal adapter should be active by default",
+ defaultAdapters.get(new AdapterKey(BigDecimal.class, String.class)));
+ // Disabled: BigDecimal adapter is OFF
+ final LazyConverterMap disabledAdapters = new LazyConverterMap();
+ disabledAdapters.setUseBigDecimalStringAdapter(false);
+ assertNull("BigDecimal adapter should be null when flag is false",
+ disabledAdapters.get(new AdapterKey(BigDecimal.class, String.class)));
+ }
+
+ @Test
+ public void bigIntegerStringAdapterFlagControlsBigInteger() {
+ // Default: BigInteger adapter is ON (useBigIntegerStringAdapter=true)
+ final LazyConverterMap adapters = new LazyConverterMap();
+ assertNotNull("BigInteger adapter should be active by default",
+ adapters.get(new AdapterKey(BigInteger.class, String.class)));
+ // Disabled: BigInteger adapter is OFF
+ final LazyConverterMap adapters2 = new LazyConverterMap();
+ adapters2.setUseBigIntegerStringAdapter(false);
+ assertNull("BigInteger adapter should be null when flag is false",
+ adapters2.get(new AdapterKey(BigInteger.class, String.class)));
+ }
+
+ /**
+ * With useBigDecimalStringAdapter=true (default), BigDecimal fields
+ * should be serialized as JSON strings using plain notation.
+ */
+ @Test
+ public void bigDecimalDefaultSerializesAsString() {
+ try (final Mapper mapper = new MapperBuilder().build()) {
+ final BigDecimalHolder holder = new BigDecimalHolder();
+ holder.score = new BigDecimal("0.000000733915");
+ final String json = mapper.writeObjectAsString(holder);
+ // Default: BigDecimal as string with plain notation (I-JSON interoperability)
+ assertEquals("{\"score\":\"0.000000733915\"}", json);
+ }
+ }
+
+ /**
+ * With useBigDecimalStringAdapter=false, BigDecimal fields should be
+ * serialized as JSON numbers (strict JSON-B 3.0 / TCK compliance).
+ */
+ @Test
+ public void bigDecimalWithAdapterDisabledSerializesAsNumber() {
+ try (final Mapper mapper = new MapperBuilder()
+ .setUseBigDecimalStringAdapter(false)
+ .build()) {
+ final BigDecimalHolder holder = new BigDecimalHolder();
+ holder.score = new BigDecimal("0.000000733915");
+ final String json = mapper.writeObjectAsString(holder);
+ // Adapter disabled: BigDecimal as JSON number (scientific notation is valid per RFC 8259)
+ assertEquals("{\"score\":7.33915E-7}", json);
+ }
+ }
+
public static class Holder {
public long value;
}
@@ -53,4 +135,8 @@ public class NumberSerializationTest {
public static class Num {
public Number value;
}
+
+ public static class BigDecimalHolder {
+ public BigDecimal score;
+ }
}