Skip to content
This repository was archived by the owner on Nov 7, 2019. It is now read-only.

Commit 20eef9d

Browse files
committed
Fixes to nanosecond serialization to use BigDecimal where possible, necessary to make Avro/Protobuf serialization work
1 parent 530b2cd commit 20eef9d

16 files changed

+100
-75
lines changed

release-notes/VERSION

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Project: jackson-datatype-jsr310
88

99
#63: Should not leak `DateTimeException`s to caller
1010
(reported by Devin S)
11+
- Change nano-second serializations to use `BigDecimal`-based write method
12+
(instead of `JsonGenerator.writeNumber(String)`, to support data formats
13+
that do not allow "numbers as Text" (like Avro, protobuf)
1114

1215
2.7.2 (27-Feb-2016)
1316

src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
*/
2727
public final class DecimalUtils
2828
{
29-
private static final char[] ZEROES = new char[] {'0', '0', '0', '0', '0', '0', '0', '0', '0'};
30-
3129
private static final BigDecimal ONE_BILLION = new BigDecimal(1_000_000_000L);
3230

3331
private DecimalUtils()
@@ -40,16 +38,66 @@ public static String toDecimal(long seconds, int nanoseconds)
4038
StringBuilder sb = new StringBuilder(20)
4139
.append(seconds)
4240
.append('.');
43-
String nanos = Integer.toString(nanoseconds);
44-
if (nanos.length() < 9) {
45-
sb.append(ZEROES, 0, 9 - nanos.length());
41+
// 14-Mar-2016, tatu: Although we do not yet (with 2.7) trim trailing zeroes,
42+
// for general case,
43+
if (nanoseconds == 0L) {
44+
// !!! TODO: 14-Mar-2016, tatu: as per [datatype-jsr310], should trim
45+
// trailing zeroes
46+
if (seconds == 0L) {
47+
return "0.0";
48+
}
49+
50+
// sb.append('0');
51+
sb.append("000000000");
52+
} else {
53+
StringBuilder nanoSB = new StringBuilder(9);
54+
nanoSB.append(nanoseconds);
55+
// May need to both prepend leading nanos (if value less than 0.1)
56+
final int nanosLen = nanoSB.length();
57+
int prepZeroes = 9 - nanosLen;
58+
while (prepZeroes > 0) {
59+
--prepZeroes;
60+
sb.append('0');
61+
}
62+
63+
// !!! TODO: 14-Mar-2016, tatu: as per [datatype-jsr310], should trim
64+
// trailing zeroes
65+
/*
66+
// AND possibly trim trailing ones
67+
int i = nanosLen;
68+
while ((i > 1) && nanoSB.charAt(i-1) == '0') {
69+
--i;
70+
}
71+
if (i < nanosLen) {
72+
nanoSB.setLength(i);
73+
}
74+
*/
75+
sb.append(nanoSB);
4676
}
47-
sb.append(nanos);
4877
return sb.toString();
4978
}
5079

80+
/**
81+
* @since 2.7.3
82+
*/
83+
public static BigDecimal toBigDecimal(long seconds, int nanoseconds)
84+
{
85+
if (nanoseconds == 0L) {
86+
// 14-Mar-2015, tatu: Let's retain one zero to avoid interpretation
87+
// as integral number
88+
if (seconds == 0L) { // except for "0.0" where it can not be done without scientific notation
89+
return BigDecimal.ZERO.setScale(1);
90+
}
91+
return BigDecimal.valueOf(seconds).setScale(9);
92+
}
93+
return new BigDecimal(toDecimal(seconds, nanoseconds));
94+
}
95+
5196
public static int extractNanosecondDecimal(BigDecimal value, long integer)
5297
{
98+
// !!! 14-Mar-2016, tatu: Somewhat inefficient; should replace with functionally
99+
// equivalent code that just subtracts integral part? (or, measure and show
100+
// there's no difference and do nothing... )
53101
return value.subtract(new BigDecimal(integer)).multiply(ONE_BILLION).intValue();
54102
}
55103
}

src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/DurationSerializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void serialize(Duration duration, JsonGenerator generator, SerializerProv
6262
{
6363
if (useTimestamp(provider)) {
6464
if(provider.isEnabled(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
65-
generator.writeNumber(DecimalUtils.toDecimal(
65+
generator.writeNumber(DecimalUtils.toBigDecimal(
6666
duration.getSeconds(), duration.getNano()
6767
));
6868
} else {

src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void serialize(T value, JsonGenerator generator, SerializerProvider provi
8080
{
8181
if (useTimestamp(provider)) {
8282
if (provider.isEnabled(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
83-
generator.writeNumber(DecimalUtils.toDecimal(
83+
generator.writeNumber(DecimalUtils.toBigDecimal(
8484
getEpochSeconds.applyAsLong(value), getNanoseconds.applyAsInt(value)
8585
));
8686
} else {

src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
public class ModuleTestBase
66
{
7+
// 14-Mar-2016, tatu: Serialization of trailing zeroes may change [datatype-jsr310#67]
8+
// Note, tho, that "0.0" itself is special case; need to avoid scientific notation:
9+
final static String NO_NANOSECS_SER = "0.0";
10+
final static String NO_NANOSECS_SUFFIX = ".000000000";
11+
712
protected ObjectMapper newMapper() {
813
return new ObjectMapper()
914
.registerModule(new JavaTimeModule());

src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,27 @@
66

77
import static org.junit.Assert.*;
88

9-
public class TestDecimalUtils
9+
public class TestDecimalUtils extends ModuleTestBase
1010
{
1111
@Test
1212
public void testToDecimal01()
1313
{
1414
String decimal = DecimalUtils.toDecimal(0, 0);
15+
assertEquals("The returned decimal is not correct.", NO_NANOSECS_SER, decimal);
1516

16-
assertEquals("The returned decimal is not correct.", "0.000000000", decimal);
17-
}
18-
19-
@Test
20-
public void testToDecimal02()
21-
{
22-
String decimal = DecimalUtils.toDecimal(15, 72);
23-
17+
decimal = DecimalUtils.toDecimal(15, 72);
2418
assertEquals("The returned decimal is not correct.", "15.000000072", decimal);
25-
}
26-
27-
@Test
28-
public void testToDecimal03()
29-
{
30-
String decimal = DecimalUtils.toDecimal(19827342231L, 192837465);
3119

20+
decimal = DecimalUtils.toDecimal(19827342231L, 192837465);
3221
assertEquals("The returned decimal is not correct.", "19827342231.192837465", decimal);
33-
}
34-
35-
@Test
36-
public void testToDecimal04()
37-
{
38-
String decimal = DecimalUtils.toDecimal(19827342231L, 0);
3922

40-
assertEquals("The returned decimal is not correct.", "19827342231.000000000", decimal);
41-
}
42-
43-
@Test
44-
public void testToDecimal05()
45-
{
46-
String decimal = DecimalUtils.toDecimal(19827342231L, 999999999);
23+
decimal = DecimalUtils.toDecimal(19827342231L, 0);
24+
assertEquals("The returned decimal is not correct.",
25+
"19827342231"+NO_NANOSECS_SUFFIX, decimal);
4726

48-
assertEquals("The returned decimal is not correct.", "19827342231.999999999", decimal);
27+
decimal = DecimalUtils.toDecimal(19827342231L, 999888000);
28+
assertEquals("The returned decimal is not correct.",
29+
"19827342231.999888000", decimal);
4930
}
5031

5132
@Test

src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDurationSerialization.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void testSerializationAsTimestampNanoseconds01() throws Exception
3232
.writeValueAsString(duration);
3333

3434
assertNotNull("The value should not be null.", value);
35-
assertEquals("The value is not correct.", "60.000000000", value);
35+
assertEquals("The value is not correct.", "60"+NO_NANOSECS_SUFFIX, value);
3636
}
3737

3838
@Test

src/test/java/com/fasterxml/jackson/datatype/jsr310/TestInstantSerialization.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void testSerializationAsTimestamp01Nanoseconds() throws Exception
6161
.writeValueAsString(date);
6262

6363
assertNotNull("The value should not be null.", value);
64-
assertEquals("The value is not correct.", "0.000000000", value);
64+
assertEquals("The value is not correct.", NO_NANOSECS_SER, value);
6565
}
6666

6767
@Test

src/test/java/com/fasterxml/jackson/datatype/jsr310/TestOffsetDateTimeSerialization.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void testSerializationAsTimestamp01Nanoseconds() throws Exception
7777
.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
7878
.with(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
7979
.writeValueAsString(date);
80-
assertEquals("The value is not correct.", "0.000000000", value);
80+
assertEquals("The value is not correct.", "0.0", value);
8181
}
8282

8383
@Test

src/test/java/com/fasterxml/jackson/datatype/jsr310/TestZonedDateTimeSerialization.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public void testSerializationAsTimestamp01Nanoseconds() throws Exception
8181
.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
8282
.with(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
8383
.writeValueAsString(date);
84-
assertEquals("The value is not correct.", "0.000000000", value);
84+
assertEquals("The value is not correct.", "0.0", value);
8585
}
8686

8787
@Test

0 commit comments

Comments
 (0)