InstantSerializerBase.java
/*
* Copyright 2013 FasterXML.com
*
* Licensed 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 tools.jackson.databind.ext.javatime.ser;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import com.fasterxml.jackson.annotation.JsonFormat;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonToken;
import tools.jackson.core.JsonParser.NumberType;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.ext.javatime.util.DecimalUtils;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsonFormatVisitors.JsonIntegerFormatVisitor;
import tools.jackson.databind.jsonFormatVisitors.JsonNumberFormatVisitor;
import tools.jackson.databind.jsonFormatVisitors.JsonValueFormat;
/**
* Base class for serializers used for {@link java.time.Instant} and
* other {@link Temporal} subtypes.
*/
public abstract class InstantSerializerBase<T extends Temporal>
extends JSR310FormattedSerializerBase<T>
{
private final DateTimeFormatter defaultFormat;
private final ToLongFunction<T> getEpochMillis;
private final ToLongFunction<T> getEpochSeconds;
private final ToIntFunction<T> getNanoseconds;
protected InstantSerializerBase(Class<T> supportedType, ToLongFunction<T> getEpochMillis,
ToLongFunction<T> getEpochSeconds, ToIntFunction<T> getNanoseconds,
DateTimeFormatter defaultFormat)
{
// Bit complicated, just because we actually want to "hide" default formatter,
// so that it won't accidentally force use of textual presentation
super(supportedType, null);
this.defaultFormat = defaultFormat;
this.getEpochMillis = getEpochMillis;
this.getEpochSeconds = getEpochSeconds;
this.getNanoseconds = getNanoseconds;
}
protected InstantSerializerBase(InstantSerializerBase<T> base,
DateTimeFormatter dtf,
Boolean useTimestamp, Boolean useNanoseconds,
JsonFormat.Shape shape)
{
super(base, dtf, useTimestamp, useNanoseconds, shape);
defaultFormat = base.defaultFormat;
getEpochMillis = base.getEpochMillis;
getEpochSeconds = base.getEpochSeconds;
getNanoseconds = base.getNanoseconds;
}
@Override
protected abstract JSR310FormattedSerializerBase<?> withFormat(DateTimeFormatter dtf,
Boolean useTimestamp,
JsonFormat.Shape shape);
@Override
public void serialize(T value, JsonGenerator generator, SerializationContext ctxt)
throws JacksonException
{
// Apply millisecond truncation if enabled
if (ctxt.isEnabled(DateTimeFeature.TRUNCATE_TO_MSECS_ON_WRITE)) {
value = _truncateToMillis(value);
}
if (useTimestamp(ctxt)) {
if (useNanoseconds(ctxt)) {
generator.writeNumber(DecimalUtils.toBigDecimal(
getEpochSeconds.applyAsLong(value), getNanoseconds.applyAsInt(value)
));
return;
}
generator.writeNumber(getEpochMillis.applyAsLong(value));
return;
}
generator.writeString(formatValue(value, ctxt));
}
// Overridden to ensure that our timestamp handling is as expected
@Override
protected void _acceptTimestampVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
{
if (useNanoseconds(visitor.getContext())) {
JsonNumberFormatVisitor v2 = visitor.expectNumberFormat(typeHint);
if (v2 != null) {
v2.numberType(NumberType.BIG_DECIMAL);
}
} else {
JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint);
if (v2 != null) {
v2.numberType(NumberType.LONG);
v2.format(JsonValueFormat.UTC_MILLISEC);
}
}
}
@Override
protected JsonToken serializationShape(SerializationContext ctxt) {
if (useTimestamp(ctxt)) {
if (useNanoseconds(ctxt)) {
return JsonToken.VALUE_NUMBER_FLOAT;
}
return JsonToken.VALUE_NUMBER_INT;
}
return JsonToken.VALUE_STRING;
}
protected String formatValue(T value, SerializationContext ctxt)
{
DateTimeFormatter formatter = (_formatter == null) ? defaultFormat :_formatter;
if (formatter != null) {
if (formatter.getZone() == null) { // timezone set if annotated on property
// If the user specified to use the context TimeZone explicitly, and the formatter provided doesn't contain a TZ
// Then we use the TZ specified in the objectMapper
if (ctxt.getConfig().hasExplicitTimeZone()
&& ctxt.isEnabled(DateTimeFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE)) {
formatter = formatter.withZone(ctxt.getTimeZone().toZoneId());
}
}
return formatter.format(value);
}
return value.toString();
}
@SuppressWarnings("unchecked")
protected T _truncateToMillis(T value) {
// Handle concrete types that support truncation
if (value instanceof java.time.Instant inst) {
return (T) inst.truncatedTo(ChronoUnit.MILLIS);
} else if (value instanceof java.time.OffsetDateTime odt) {
return (T) odt.truncatedTo(ChronoUnit.MILLIS);
} else if (value instanceof java.time.ZonedDateTime zdt) {
return (T) zdt.truncatedTo(ChronoUnit.MILLIS);
}
// Return as-is if type doesn't support truncation
return value;
}
}