Skip to content

Commit c7fd2db

Browse files
authored
Merge pull request #134 from jeanouii/fix/big-decimal-integer-swapped-and-consistency
fix(JOHNZON-426): BigInteger/BigDecimal string adapter flags swapped
2 parents 5612282 + 9f11b4b commit c7fd2db

File tree

6 files changed

+109
-9
lines changed

6 files changed

+109
-9
lines changed

johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,12 @@ private MapperConverter findConverter(final boolean copyDate, final AccessMode.D
705705

706706
if (Date.class.isAssignableFrom(type) && copyDate) {
707707
converter = new DateWithCopyConverter(Adapter.class.cast(adapters.get(new AdapterKey(Date.class, String.class))));
708+
} else if (type == BigDecimal.class || type == BigInteger.class) {
709+
// BigDecimal/BigInteger are "primitives" in the mapper so they bypass
710+
// config.findAdapter() in writeValue(). Use a direct get() to trigger
711+
// lazy loading and respect useBigDecimalStringAdapter/useBigIntegerStringAdapter.
712+
// this makes it symetric with READ
713+
converter = adapters.get(new AdapterKey(type, String.class));
708714
} else {
709715
for (final Map.Entry<AdapterKey, Adapter<?, ?>> adapterEntry : adapters.entrySet()) {
710716
if (adapterEntry.getKey().getFrom() == adapterEntry.getKey().getTo()) { // String -> String

johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/BigDecimalConverter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
public class BigDecimalConverter implements Converter<BigDecimal> {
2626
@Override
2727
public String toString(final BigDecimal instance) {
28-
return instance.toString();
28+
// when using the converter, user expects the decimal notation
29+
// otherwise, JsonNumber will give the E (scientific) notation
30+
return instance.toPlainString();
2931
}
3032

3133
@Override

johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ public Object from(final Object a) {
8585

8686
private boolean useShortISO8601Format = true;
8787
private DateTimeFormatter dateTimeFormatter;
88-
private boolean useBigIntegerStringAdapter = true;
89-
private boolean useBigDecimalStringAdapter = true;
88+
// I-JSON (RFC 7493 Section 2.2): BigX exceed IEEE 754 double, string is safer by default.
89+
// Set -Djohnzon.use-big-number-stringadapter=false for strict JSON-B 3.0 / TCK compliance.
90+
private static final boolean IJSON_BIG_NUMBER_DEFAULT = !Boolean.getBoolean("johnzon.use-big-number-stringadapter.disabled");
91+
private boolean useBigIntegerStringAdapter = IJSON_BIG_NUMBER_DEFAULT;
92+
private boolean useBigDecimalStringAdapter = IJSON_BIG_NUMBER_DEFAULT;
9093

9194
public void setUseShortISO8601Format(final boolean useShortISO8601Format) {
9295
this.useShortISO8601Format = useShortISO8601Format;
@@ -163,10 +166,10 @@ public Set<AdapterKey> adapterKeys() {
163166
if (from == String.class) {
164167
return add(key, new ConverterAdapter<>(new StringConverter(), String.class));
165168
}
166-
if (from == BigDecimal.class && useBigIntegerStringAdapter) {
169+
if (from == BigDecimal.class && useBigDecimalStringAdapter) {
167170
return add(key, new ConverterAdapter<>(new BigDecimalConverter(), BigDecimal.class));
168171
}
169-
if (from == BigInteger.class && useBigDecimalStringAdapter) {
172+
if (from == BigInteger.class && useBigIntegerStringAdapter) {
170173
return add(key, new ConverterAdapter<>(new BigIntegerConverter(), BigInteger.class));
171174
}
172175
if (from == Locale.class) {

johnzon-mapper/src/test/java/org/apache/johnzon/mapper/LiteralTest.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ public int compare(final String o1, final String o2) {
5555
return expectedJson.indexOf(o1) - expectedJson.indexOf(o2);
5656
}
5757
};
58-
new MapperBuilder().setAttributeOrder(attributeOrder).build().writeObject(nc, sw);
58+
new MapperBuilder().setAttributeOrder(attributeOrder)
59+
.setUseBigDecimalStringAdapter(false).setUseBigIntegerStringAdapter(false)
60+
.build().writeObject(nc, sw);
5961
assertEquals(expectedJson, sw.toString());
60-
final NumberClass read = new MapperBuilder().setAttributeOrder(attributeOrder).build()
61-
.readObject(new StringReader(sw.toString()), NumberClass.class);
62+
final NumberClass read = new MapperBuilder().setAttributeOrder(attributeOrder)
63+
.setUseBigDecimalStringAdapter(false).setUseBigIntegerStringAdapter(false)
64+
.build().readObject(new StringReader(sw.toString()), NumberClass.class);
6265
assertEquals(nc, read);
6366

6467
}

johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperEnhancedTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public void writeTestclass() {
137137
public int compare(String o1, String o2) {
138138
return json.indexOf(o1) - json.indexOf(o2);
139139
}
140-
}).build().writeObject(tc2, sw);
140+
}).setUseBigDecimalStringAdapter(false).build().writeObject(tc2, sw);
141141
assertEquals(json, sw.toString());
142142
}
143143

johnzon-mapper/src/test/java/org/apache/johnzon/mapper/NumberSerializationTest.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@
1919
package org.apache.johnzon.mapper;
2020

2121
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertNotNull;
23+
import static org.junit.Assert.assertNull;
2224
import static org.junit.Assert.assertTrue;
2325

2426
import java.math.BigDecimal;
27+
import java.math.BigInteger;
2528

29+
import org.apache.johnzon.mapper.converter.BigDecimalConverter;
30+
import org.apache.johnzon.mapper.internal.AdapterKey;
31+
import org.apache.johnzon.mapper.map.LazyConverterMap;
2632
import org.junit.Test;
2733

2834
public class NumberSerializationTest {
@@ -46,11 +52,91 @@ public void numberFromJson() {
4652
mapper.close();
4753
}
4854

55+
/**
56+
* Bug: BigDecimalConverter used toString() which produces scientific notation
57+
* (e.g. "7.33915E-7"). Should use toPlainString() to produce "0.000000733915".
58+
*/
59+
@Test
60+
public void bigDecimalConverterUsesPlainNotation() {
61+
final BigDecimalConverter converter = new BigDecimalConverter();
62+
final BigDecimal smallValue = new BigDecimal("0.000000733915");
63+
final String result = converter.toString(smallValue);
64+
assertEquals("BigDecimalConverter should use plain notation, not scientific",
65+
"0.000000733915", result);
66+
}
67+
68+
/**
69+
* Bug fix: useBigDecimalStringAdapter and useBigIntegerStringAdapter flags
70+
* were swapped in LazyConverterMap. Each flag must control its own type.
71+
* Both default to true (string) for I-JSON (RFC 7493) interoperability.
72+
*/
73+
@Test
74+
public void bigDecimalStringAdapterFlagControlsBigDecimal() {
75+
// Default: BigDecimal adapter is ON (useBigDecimalStringAdapter=true)
76+
final LazyConverterMap defaultAdapters = new LazyConverterMap();
77+
assertNotNull("BigDecimal adapter should be active by default",
78+
defaultAdapters.get(new AdapterKey(BigDecimal.class, String.class)));
79+
// Disabled: BigDecimal adapter is OFF
80+
final LazyConverterMap disabledAdapters = new LazyConverterMap();
81+
disabledAdapters.setUseBigDecimalStringAdapter(false);
82+
assertNull("BigDecimal adapter should be null when flag is false",
83+
disabledAdapters.get(new AdapterKey(BigDecimal.class, String.class)));
84+
}
85+
86+
@Test
87+
public void bigIntegerStringAdapterFlagControlsBigInteger() {
88+
// Default: BigInteger adapter is ON (useBigIntegerStringAdapter=true)
89+
final LazyConverterMap adapters = new LazyConverterMap();
90+
assertNotNull("BigInteger adapter should be active by default",
91+
adapters.get(new AdapterKey(BigInteger.class, String.class)));
92+
// Disabled: BigInteger adapter is OFF
93+
final LazyConverterMap adapters2 = new LazyConverterMap();
94+
adapters2.setUseBigIntegerStringAdapter(false);
95+
assertNull("BigInteger adapter should be null when flag is false",
96+
adapters2.get(new AdapterKey(BigInteger.class, String.class)));
97+
}
98+
99+
/**
100+
* With useBigDecimalStringAdapter=true (default), BigDecimal fields
101+
* should be serialized as JSON strings using plain notation.
102+
*/
103+
@Test
104+
public void bigDecimalDefaultSerializesAsString() {
105+
try (final Mapper mapper = new MapperBuilder().build()) {
106+
final BigDecimalHolder holder = new BigDecimalHolder();
107+
holder.score = new BigDecimal("0.000000733915");
108+
final String json = mapper.writeObjectAsString(holder);
109+
// Default: BigDecimal as string with plain notation (I-JSON interoperability)
110+
assertEquals("{\"score\":\"0.000000733915\"}", json);
111+
}
112+
}
113+
114+
/**
115+
* With useBigDecimalStringAdapter=false, BigDecimal fields should be
116+
* serialized as JSON numbers (strict JSON-B 3.0 / TCK compliance).
117+
*/
118+
@Test
119+
public void bigDecimalWithAdapterDisabledSerializesAsNumber() {
120+
try (final Mapper mapper = new MapperBuilder()
121+
.setUseBigDecimalStringAdapter(false)
122+
.build()) {
123+
final BigDecimalHolder holder = new BigDecimalHolder();
124+
holder.score = new BigDecimal("0.000000733915");
125+
final String json = mapper.writeObjectAsString(holder);
126+
// Adapter disabled: BigDecimal as JSON number (scientific notation is valid per RFC 8259)
127+
assertEquals("{\"score\":7.33915E-7}", json);
128+
}
129+
}
130+
49131
public static class Holder {
50132
public long value;
51133
}
52134

53135
public static class Num {
54136
public Number value;
55137
}
138+
139+
public static class BigDecimalHolder {
140+
public BigDecimal score;
141+
}
56142
}

0 commit comments

Comments
 (0)