JSR310FormattedSerializerBase.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.util.List;
import java.util.Locale;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.jsonFormatVisitors.*;
/**
* Base class that provides an array schema instead of scalar schema if
* {@link SerializationFeature#WRITE_DATES_AS_TIMESTAMPS} is enabled.
*
* @author Nick Williams
*/
abstract class JSR310FormattedSerializerBase<T>
extends JSR310SerializerBase<T>
{
/**
* Flag that indicates that serialization must be done as the
* Java timestamp, regardless of other settings.
*/
protected final Boolean _useTimestamp;
/**
* Flag that indicates that numeric timestamp values must be written using
* nanosecond timestamps if the datatype supports such resolution,
* regardless of other settings.
*/
protected final Boolean _useNanoseconds;
/**
* Specific format to use, if not default format: non-null value
* also indicates that serialization is to be done as JSON String,
* not numeric timestamp, unless {@code #_useTimestamp} is true.
*/
protected final DateTimeFormatter _formatter;
protected final JsonFormat.Shape _shape;
/**
* Lazily constructed {@code JavaType} representing type
* {@code List<Integer>}.
*/
protected transient volatile JavaType _integerListType;
protected JSR310FormattedSerializerBase(Class<T> supportedType) {
this(supportedType, null);
}
protected JSR310FormattedSerializerBase(Class<T> supportedType,
DateTimeFormatter formatter) {
super(supportedType);
_useTimestamp = null;
_useNanoseconds = null;
_shape = null;
_formatter = formatter;
}
protected JSR310FormattedSerializerBase(JSR310FormattedSerializerBase<?> base,
DateTimeFormatter dtf,
Boolean useTimestamp, Boolean useNanoseconds,
JsonFormat.Shape shape)
{
super(base.handledType());
_useTimestamp = useTimestamp;
_useNanoseconds = useNanoseconds;
_formatter = dtf;
_shape = shape;
}
protected abstract JSR310FormattedSerializerBase<?> withFormat(DateTimeFormatter dtf,
Boolean useTimestamp, JsonFormat.Shape shape);
protected JSR310FormattedSerializerBase<?> withFeatures(Boolean writeZoneId,
Boolean writeNanoseconds) {
return this;
}
@Override
public ValueSerializer<?> createContextual(SerializationContext ctxt,
BeanProperty property)
{
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
if (format != null) {
Boolean useTimestamp = null;
// Simple case first: serialize as numeric timestamp?
JsonFormat.Shape shape = format.getShape();
if (shape == JsonFormat.Shape.ARRAY || shape.isNumeric() ) {
useTimestamp = Boolean.TRUE;
} else {
useTimestamp = (shape == JsonFormat.Shape.STRING) ? Boolean.FALSE : null;
}
DateTimeFormatter dtf = _formatter;
// If not, do we have a pattern?
if (format.hasPattern()) {
dtf = _useDateTimeFormatter(ctxt, format);
}
JSR310FormattedSerializerBase<?> ser = this;
if ((shape != _shape) || (useTimestamp != _useTimestamp) || (dtf != _formatter)) {
ser = ser.withFormat(dtf, useTimestamp, shape);
}
Boolean writeZoneId = format.getFeature(JsonFormat.Feature.WRITE_DATES_WITH_ZONE_ID);
Boolean writeNanoseconds = format.getFeature(JsonFormat.Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
if ((writeZoneId != null) || (writeNanoseconds != null)) {
ser = ser.withFeatures(writeZoneId, writeNanoseconds);
}
return ser;
}
return this;
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
{
if (useTimestamp(visitor.getContext())) {
_acceptTimestampVisitor(visitor, typeHint);
} else {
JsonStringFormatVisitor v2 = visitor.expectStringFormat(typeHint);
if (v2 != null) {
v2.format(JsonValueFormat.DATE_TIME);
}
}
}
protected void _acceptTimestampVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
{
// By default, most sub-types use JSON Array, so do this:
// 28-May-2019, tatu: serialized as a List<Integer>, presumably
JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(_integerListType(visitor.getContext()));
if (v2 != null) {
v2.itemsFormat(JsonFormatTypes.INTEGER);
}
}
protected JavaType _integerListType(SerializationContext ctxt) {
JavaType t = _integerListType;
if (t == null) {
t = ctxt.getTypeFactory()
.constructCollectionType(List.class, Integer.class);
_integerListType = t;
}
return t;
}
/**
* Overridable method that determines {@link SerializationFeature} that is used as
* the global default in determining if date/time value serialized should use numeric
* format ("timestamp") or not.
*<p>
* Note that this feature is just the baseline setting and may be overridden on per-type
* or per-property basis.
*/
protected DateTimeFeature getTimestampsFeature() {
return DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS;
}
protected boolean useTimestamp(SerializationContext ctxt) {
if (_useTimestamp != null) {
return _useTimestamp.booleanValue();
}
if (_shape != null) {
if (_shape == Shape.STRING) {
return false;
}
if (_shape == Shape.NUMBER_INT) {
return true;
}
}
// assume that explicit formatter definition implies use of textual format
return (_formatter == null) && useTimestampFromGlobalDefaults(ctxt);
}
protected boolean useTimestampFromGlobalDefaults(SerializationContext ctxt) {
return (ctxt != null)
&& ctxt.isEnabled(getTimestampsFeature());
}
protected boolean _useTimestampExplicitOnly(SerializationContext ctxt) {
if (_useTimestamp != null) {
return _useTimestamp.booleanValue();
}
return false;
}
protected boolean useNanoseconds(SerializationContext ctxt) {
if (_useNanoseconds != null) {
return _useNanoseconds.booleanValue();
}
if (_shape != null) {
if (_shape == Shape.NUMBER_INT) {
return false;
}
if (_shape == Shape.NUMBER_FLOAT) {
return true;
}
}
return (ctxt != null)
&& ctxt.isEnabled(DateTimeFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
}
// modules-java8#189: to be overridden by other formatters using this as base class
protected DateTimeFormatter _useDateTimeFormatter(SerializationContext ctxt,
JsonFormat.Value format)
{
DateTimeFormatter dtf;
final String pattern = format.getPattern();
final Locale locale = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
if (locale == null) {
dtf = DateTimeFormatter.ofPattern(pattern);
} else {
dtf = DateTimeFormatter.ofPattern(pattern, locale);
}
//Issue #69: For instant serializers/deserializers we need to configure the formatter with
//a time zone picked up from JsonFormat annotation, otherwise serialization might not work
if (format.hasTimeZone()) {
dtf = dtf.withZone(format.getTimeZone().toZoneId());
}
return dtf;
}
}