Skip to content

Commit ed4c4ad

Browse files
committed
Support unit pattern in duration serializer. ref FasterXML#189
1 parent f2f7686 commit ed4c4ad

File tree

4 files changed

+277
-29
lines changed

4 files changed

+277
-29
lines changed

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

+68-15
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@
2121
import com.fasterxml.jackson.core.JsonParser;
2222
import com.fasterxml.jackson.core.JsonToken;
2323

24+
import com.fasterxml.jackson.databind.BeanProperty;
2425
import com.fasterxml.jackson.databind.JavaType;
2526
import com.fasterxml.jackson.databind.JsonMappingException;
27+
import com.fasterxml.jackson.databind.JsonSerializer;
2628
import com.fasterxml.jackson.databind.SerializationFeature;
2729
import com.fasterxml.jackson.databind.SerializerProvider;
2830
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
2931
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonIntegerFormatVisitor;
3032
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
3133
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
34+
import com.fasterxml.jackson.datatype.jsr310.util.DurationUnitConverter;
3235

3336
import java.io.IOException;
3437
import java.math.BigDecimal;
@@ -52,6 +55,16 @@ public class DurationSerializer extends JSR310FormattedSerializerBase<Duration>
5255

5356
public static final DurationSerializer INSTANCE = new DurationSerializer();
5457

58+
/**
59+
* When defined (not {@code null}) duration values will be converted into integers
60+
* with the unit configured for the converter.
61+
* Only available when {@link SerializationFeature#WRITE_DURATIONS_AS_TIMESTAMPS} is enabled
62+
* and {@link SerializationFeature#WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS} is not enabled
63+
* since the duration converters do not support fractions
64+
* @since 2.12
65+
*/
66+
private DurationUnitConverter _durationUnitConverter;
67+
5568
private DurationSerializer() {
5669
super(Duration.class);
5770
}
@@ -66,45 +79,80 @@ protected DurationSerializer(DurationSerializer base,
6679
super(base, useTimestamp, useNanoseconds, dtf, null);
6780
}
6881

82+
protected DurationSerializer(DurationSerializer base, DurationUnitConverter converter) {
83+
super(base, base._useTimestamp, base._useNanoseconds, base._formatter, base._shape);
84+
_durationUnitConverter = converter;
85+
}
86+
6987
@Override
7088
protected DurationSerializer withFormat(Boolean useTimestamp, DateTimeFormatter dtf, JsonFormat.Shape shape) {
7189
return new DurationSerializer(this, useTimestamp, dtf);
7290
}
7391

92+
protected DurationSerializer withConverter(DurationUnitConverter converter) {
93+
return new DurationSerializer(this, converter);
94+
}
95+
7496
// @since 2.10
7597
@Override
7698
protected SerializationFeature getTimestampsFeature() {
7799
return SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS;
78100
}
79101

102+
@Override
103+
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
104+
DurationSerializer ser = (DurationSerializer) super.createContextual(prov, property);
105+
JsonFormat.Value format = findFormatOverrides(prov, property, handledType());
106+
if (format != null && format.hasPattern()) {
107+
final String pattern = format.getPattern();
108+
DurationUnitConverter p = DurationUnitConverter.from(pattern);
109+
if (p == null) {
110+
prov.reportBadDefinition(handledType(),
111+
String.format(
112+
"Bad 'pattern' definition (\"%s\") for `Duration`: expected one of [%s]",
113+
pattern, DurationUnitConverter.descForAllowed()));
114+
}
115+
116+
ser = ser.withConverter(p);
117+
}
118+
return ser;
119+
}
120+
80121
@Override
81122
public void serialize(Duration duration, JsonGenerator generator, SerializerProvider provider) throws IOException
82123
{
83124
if (useTimestamp(provider)) {
84125
if (useNanoseconds(provider)) {
85-
// 20-Oct-2020, tatu: [modules-java8#165] Need to take care of
86-
// negative values too, and without work-around values
87-
// returned are wonky wrt conversions
88-
BigDecimal bd;
89-
if (duration.isNegative()) {
90-
duration = duration.abs();
91-
bd = DecimalUtils.toBigDecimal(duration.getSeconds(),
92-
duration.getNano())
93-
.negate();
126+
generator.writeNumber(_toNanos(duration));
127+
} else {
128+
if (_durationUnitConverter != null) {
129+
generator.writeNumber(_durationUnitConverter.convert(duration));
94130
} else {
95-
bd = DecimalUtils.toBigDecimal(duration.getSeconds(),
96-
duration.getNano());
131+
generator.writeNumber(duration.toMillis());
97132
}
98-
generator.writeNumber(bd);
99-
} else {
100-
generator.writeNumber(duration.toMillis());
101133
}
102134
} else {
103-
// Does not look like we can make any use of DateTimeFormatter here?
104135
generator.writeString(duration.toString());
105136
}
106137
}
107138

139+
// 20-Oct-2020, tatu: [modules-java8#165] Need to take care of
140+
// negative values too, and without work-around values
141+
// returned are wonky wrt conversions
142+
private BigDecimal _toNanos(Duration duration) {
143+
BigDecimal bd;
144+
if (duration.isNegative()) {
145+
duration = duration.abs();
146+
bd = DecimalUtils.toBigDecimal(duration.getSeconds(),
147+
duration.getNano())
148+
.negate();
149+
} else {
150+
bd = DecimalUtils.toBigDecimal(duration.getSeconds(),
151+
duration.getNano());
152+
}
153+
return bd;
154+
}
155+
108156
@Override
109157
protected void _acceptTimestampVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
110158
{
@@ -135,4 +183,9 @@ protected JsonToken serializationShape(SerializerProvider provider) {
135183
protected JSR310FormattedSerializerBase<?> withFeatures(Boolean writeZoneId, Boolean writeNanoseconds) {
136184
return new DurationSerializer(this, _useTimestamp, writeNanoseconds, _formatter);
137185
}
186+
187+
@Override
188+
protected DateTimeFormatter _useDateTimeFormatter(SerializerProvider prov, JsonFormat.Value format) {
189+
return null;
190+
}
138191
}

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java

+20-13
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,7 @@ public JsonSerializer<?> createContextual(SerializerProvider prov,
144144

145145
// If not, do we have a pattern?
146146
if (format.hasPattern()) {
147-
final String pattern = format.getPattern();
148-
final Locale locale = format.hasLocale() ? format.getLocale() : prov.getLocale();
149-
if (locale == null) {
150-
dtf = DateTimeFormatter.ofPattern(pattern);
151-
} else {
152-
dtf = DateTimeFormatter.ofPattern(pattern, locale);
153-
}
154-
//Issue #69: For instant serializers/deserializers we need to configure the formatter with
155-
//a time zone picked up from JsonFormat annotation, otherwise serialization might not work
156-
if (format.hasTimeZone()) {
157-
dtf = dtf.withZone(format.getTimeZone().toZoneId());
158-
}
147+
dtf = _useDateTimeFormatter(prov, format);
159148
}
160149
JSR310FormattedSerializerBase<?> ser = this;
161150
if ((shape != _shape) || (useTimestamp != _useTimestamp) || (dtf != _formatter)) {
@@ -211,7 +200,7 @@ protected JavaType _integerListType(SerializerProvider prov) {
211200
}
212201
return t;
213202
}
214-
203+
215204
/**
216205
* Overridable method that determines {@link SerializationFeature} that is used as
217206
* the global default in determining if date/time value serialized should use numeric
@@ -265,4 +254,22 @@ protected boolean useNanoseconds(SerializerProvider provider) {
265254
return (provider != null)
266255
&& provider.isEnabled(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
267256
}
257+
258+
// modules-java8#189: to be overridden by other formatters using this as base class
259+
protected DateTimeFormatter _useDateTimeFormatter(SerializerProvider prov, JsonFormat.Value format) {
260+
DateTimeFormatter dtf;
261+
final String pattern = format.getPattern();
262+
final Locale locale = format.hasLocale() ? format.getLocale() : prov.getLocale();
263+
if (locale == null) {
264+
dtf = DateTimeFormatter.ofPattern(pattern);
265+
} else {
266+
dtf = DateTimeFormatter.ofPattern(pattern, locale);
267+
}
268+
//Issue #69: For instant serializers/deserializers we need to configure the formatter with
269+
//a time zone picked up from JsonFormat annotation, otherwise serialization might not work
270+
if (format.hasTimeZone()) {
271+
dtf = dtf.withZone(format.getTimeZone().toZoneId());
272+
}
273+
return dtf;
274+
}
268275
}

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/util/DurationUnitConverter.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010

1111
import static com.fasterxml.jackson.datatype.jsr310.util.DurationUnitConverter.DurationSerialization.deserializer;
1212

13-
13+
/**
14+
* Handles the conversion of the duration based on the API of {@link Duration} for a restricted set of {@link ChronoUnit}.
15+
* Only the units considered as accurate are supported in this converter since are the only ones capable of handling
16+
* deserialization in a precise manner (see {@link ChronoUnit#isDurationEstimated}).
17+
*
18+
* @since 2.12
19+
*/
1420
public class DurationUnitConverter {
1521

1622
protected static class DurationSerialization {

0 commit comments

Comments
 (0)