-
-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support to serialize and de-serialize java.time types into Avro type …
…and logicalType.
- Loading branch information
1 parent
62570b4
commit 1bd02ba
Showing
11 changed files
with
773 additions
and
0 deletions.
There are no files selected for viewing
61 changes: 61 additions & 0 deletions
61
avro/src/main/java/com/fasterxml/jackson/dataformat/avro/jsr310/AvroJavaTimeModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.fasterxml.jackson.dataformat.avro.jsr310; | ||
|
||
import com.fasterxml.jackson.core.Version; | ||
import com.fasterxml.jackson.core.json.PackageVersion; | ||
import com.fasterxml.jackson.databind.module.SimpleModule; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.deser.AvroInstantDeserializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.deser.AvroLocalDateDeserializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.deser.AvroLocalDateTimeDeserializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.deser.AvroLocalTimeDeserializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.ser.AvroInstantSerializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.ser.AvroLocalDateSerializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.ser.AvroLocalDateTimeSerializer; | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.ser.AvroLocalTimeSerializer; | ||
|
||
import java.time.Instant; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
import java.time.OffsetDateTime; | ||
import java.time.ZonedDateTime; | ||
|
||
/** | ||
* A module that installs a collection of serializers and deserializers for java.time classes. | ||
*/ | ||
public class AvroJavaTimeModule extends SimpleModule { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public AvroJavaTimeModule() { | ||
super(PackageVersion.VERSION); | ||
|
||
addSerializer(Instant.class, AvroInstantSerializer.INSTANT); | ||
addSerializer(OffsetDateTime.class, AvroInstantSerializer.OFFSET_DATE_TIME); | ||
addSerializer(ZonedDateTime.class, AvroInstantSerializer.ZONED_DATE_TIME); | ||
addSerializer(LocalDateTime.class, AvroLocalDateTimeSerializer.INSTANCE); | ||
addSerializer(LocalDate.class, AvroLocalDateSerializer.INSTANCE); | ||
addSerializer(LocalTime.class, AvroLocalTimeSerializer.INSTANCE); | ||
|
||
addDeserializer(Instant.class, AvroInstantDeserializer.INSTANT); | ||
addDeserializer(OffsetDateTime.class, AvroInstantDeserializer.OFFSET_DATE_TIME); | ||
addDeserializer(ZonedDateTime.class, AvroInstantDeserializer.ZONED_DATE_TIME); | ||
addDeserializer(LocalDateTime.class, AvroLocalDateTimeDeserializer.INSTANCE); | ||
addDeserializer(LocalDate.class, AvroLocalDateDeserializer.INSTANCE); | ||
addDeserializer(LocalTime.class, AvroLocalTimeDeserializer.INSTANCE); | ||
} | ||
|
||
@Override | ||
public String getModuleName() { | ||
return getClass().getName(); | ||
} | ||
|
||
@Override | ||
public Version version() { | ||
return PackageVersion.VERSION; | ||
} | ||
|
||
@Override | ||
public void setupModule(SetupContext context) { | ||
super.setupModule(context); | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
...main/java/com/fasterxml/jackson/dataformat/avro/jsr310/deser/AvroInstantDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package com.fasterxml.jackson.dataformat.avro.jsr310.deser; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; | ||
|
||
import java.io.IOException; | ||
import java.time.Instant; | ||
import java.time.OffsetDateTime; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.time.temporal.Temporal; | ||
import java.util.function.BiFunction; | ||
|
||
/** | ||
* Deserializer for variants of java.time classes (Instant, OffsetDateTime, ZonedDateTime) from an integer value. | ||
* | ||
* Deserialized value represents an instant on the global timeline, independent of a particular time zone or | ||
* calendar, with a precision of one millisecond from the unix epoch, 1 January 1970 00:00:00.000 UTC. | ||
* Time zone information is lost at serialization. Time zone data types receives time zone from deserialization context. | ||
* | ||
* Deserialization from string is not supported. | ||
* | ||
* @param <T> The type of a instant class that can be deserialized. | ||
*/ | ||
public class AvroInstantDeserializer<T extends Temporal> extends StdScalarDeserializer<T> { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public static final AvroInstantDeserializer<Instant> INSTANT = | ||
new AvroInstantDeserializer<>(Instant.class, (instant, zoneID) -> instant); | ||
|
||
public static final AvroInstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = | ||
new AvroInstantDeserializer<>(OffsetDateTime.class, OffsetDateTime::ofInstant); | ||
|
||
public static final AvroInstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = | ||
new AvroInstantDeserializer<>(ZonedDateTime.class, ZonedDateTime::ofInstant); | ||
|
||
protected final BiFunction<Instant, ZoneId, T> fromInstant; | ||
|
||
protected AvroInstantDeserializer(Class<T> t, BiFunction<Instant, ZoneId, T> fromInstant) { | ||
super(t); | ||
this.fromInstant = fromInstant; | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public T deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException { | ||
final ZoneId defaultZoneId = context.getTimeZone().toZoneId().normalized(); | ||
switch (p.getCurrentToken()) { | ||
case VALUE_NUMBER_INT: | ||
return fromLong(p.getLongValue(), defaultZoneId); | ||
default: | ||
try { | ||
return (T) context.handleUnexpectedToken(_valueClass, p); | ||
} catch (JsonMappingException e) { | ||
throw e; | ||
} catch (IOException e) { | ||
throw JsonMappingException.fromUnexpectedIOE(e); | ||
} | ||
} | ||
} | ||
|
||
private T fromLong(long longValue, ZoneId defaultZoneId) { | ||
/** | ||
* Number of milliseconds, independent of a particular time zone or calendar, | ||
* from 1 January 1970 00:00:00.000 UTC. | ||
*/ | ||
return fromInstant.apply(Instant.ofEpochMilli(longValue), defaultZoneId); | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
...in/java/com/fasterxml/jackson/dataformat/avro/jsr310/deser/AvroLocalDateDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package com.fasterxml.jackson.dataformat.avro.jsr310.deser; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDate; | ||
|
||
/** | ||
* Deserializer for {@link LocalDate} from and integer value. | ||
* | ||
* Deserialized value represents number of days from the unix epoch, 1 January 1970. | ||
* | ||
* Deserialization from string is not supported. | ||
*/ | ||
public class AvroLocalDateDeserializer extends StdScalarDeserializer<LocalDate> { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public static final AvroLocalDateDeserializer INSTANCE = new AvroLocalDateDeserializer(); | ||
|
||
protected AvroLocalDateDeserializer() { | ||
super(LocalDate.class); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public LocalDate deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException { | ||
switch (p.getCurrentToken()) { | ||
case VALUE_NUMBER_INT: | ||
return fromLong(p.getLongValue()); | ||
default: | ||
try { | ||
return (LocalDate) context.handleUnexpectedToken(_valueClass, p); | ||
} catch (JsonMappingException e) { | ||
throw e; | ||
} catch (IOException e) { | ||
throw JsonMappingException.fromUnexpectedIOE(e); | ||
} | ||
} | ||
} | ||
|
||
private LocalDate fromLong(long longValue) { | ||
/** | ||
* Number of days from the unix epoch, 1 January 1970.. | ||
*/ | ||
return LocalDate.ofEpochDay(longValue); | ||
} | ||
|
||
} |
57 changes: 57 additions & 0 deletions
57
...ava/com/fasterxml/jackson/dataformat/avro/jsr310/deser/AvroLocalDateTimeDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.fasterxml.jackson.dataformat.avro.jsr310.deser; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; | ||
|
||
import java.io.IOException; | ||
import java.time.Instant; | ||
import java.time.LocalDateTime; | ||
import java.time.ZoneOffset; | ||
|
||
/** | ||
* Deserializer for {@link LocalDateTime} from an integer value. | ||
* | ||
* Deserialized value represents timestamp in a local timezone, regardless of what specific time zone | ||
* is considered local, with a precision of one millisecond from 1 January 1970 00:00:00.000. | ||
* | ||
* Deserialization from string is not supported. | ||
*/ | ||
public class AvroLocalDateTimeDeserializer extends StdScalarDeserializer<LocalDateTime> { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public static final AvroLocalDateTimeDeserializer INSTANCE = new AvroLocalDateTimeDeserializer(); | ||
|
||
protected AvroLocalDateTimeDeserializer() { | ||
super(LocalDateTime.class); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public LocalDateTime deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException { | ||
switch (p.getCurrentToken()) { | ||
case VALUE_NUMBER_INT: | ||
return fromLong(p.getLongValue()); | ||
default: | ||
try { | ||
return (LocalDateTime) context.handleUnexpectedToken(_valueClass, p); | ||
} catch (JsonMappingException e) { | ||
throw e; | ||
} catch (IOException e) { | ||
throw JsonMappingException.fromUnexpectedIOE(e); | ||
} | ||
} | ||
} | ||
|
||
private LocalDateTime fromLong(long longValue) { | ||
/** | ||
* Number of milliseconds in a local timezone, regardless of what specific time zone is considered local, | ||
* from 1 January 1970 00:00:00.000. | ||
*/ | ||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(longValue), ZoneOffset.ofTotalSeconds(0)); | ||
} | ||
|
||
} |
56 changes: 56 additions & 0 deletions
56
...in/java/com/fasterxml/jackson/dataformat/avro/jsr310/deser/AvroLocalTimeDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.fasterxml.jackson.dataformat.avro.jsr310.deser; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
|
||
/** | ||
* Deserializer for {@link LocalTime} from an integer value. | ||
* | ||
* Deserialized value represents time of day, with no reference to a particular calendar, | ||
* time zone or date, where the int stores the number of milliseconds after midnight, 00:00:00.000. | ||
* | ||
* Deserialization from string is not supported. | ||
*/ | ||
public class AvroLocalTimeDeserializer extends StdScalarDeserializer<LocalTime> { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public static final AvroLocalTimeDeserializer INSTANCE = new AvroLocalTimeDeserializer(); | ||
|
||
protected AvroLocalTimeDeserializer() { | ||
super(LocalDateTime.class); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public LocalTime deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException { | ||
switch (p.getCurrentToken()) { | ||
case VALUE_NUMBER_INT: | ||
return fromLong(p.getLongValue()); | ||
default: | ||
try { | ||
return (LocalTime) context.handleUnexpectedToken(_valueClass, p); | ||
} catch (JsonMappingException e) { | ||
throw e; | ||
} catch (IOException e) { | ||
throw JsonMappingException.fromUnexpectedIOE(e); | ||
} | ||
} | ||
} | ||
|
||
private LocalTime fromLong(long longValue) { | ||
/** | ||
* Number of milliseconds, with no reference to a particular calendar, time zone or date, after | ||
* midnight, 00:00:00.000. | ||
*/ | ||
return LocalTime.ofNanoOfDay(longValue * 1000_000L); | ||
} | ||
|
||
} |
76 changes: 76 additions & 0 deletions
76
...src/main/java/com/fasterxml/jackson/dataformat/avro/jsr310/ser/AvroInstantSerializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.fasterxml.jackson.dataformat.avro.jsr310.ser; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.SerializerProvider; | ||
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; | ||
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonIntegerFormatVisitor; | ||
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; | ||
|
||
import java.io.IOException; | ||
import java.time.Instant; | ||
import java.time.OffsetDateTime; | ||
import java.time.ZonedDateTime; | ||
import java.time.temporal.Temporal; | ||
import java.util.function.Function; | ||
|
||
/** | ||
* Serializer for variants of java.time classes (Instant, OffsetDateTime, ZonedDateTime) into long value. | ||
* | ||
* Serialized value represents an instant on the global timeline, independent of a particular time zone or | ||
* calendar, with a precision of one millisecond from the unix epoch, 1 January 1970 00:00:00.000 UTC. | ||
* Please note that time zone information gets lost in this process. Upon reading a value back, we can only | ||
* reconstruct the instant, but not the original representation. | ||
* | ||
* Note: In combination with {@link com.fasterxml.jackson.dataformat.avro.schema.DateTimeVisitor} it aims to produce | ||
* Avro schema with type long with logicalType timestamp-millis: | ||
* { | ||
* "type" : "long", | ||
* "logicalType" : "timestamp-millis" | ||
* } | ||
* | ||
* {@link AvroInstantSerializer} does not support serialization to string. | ||
* | ||
* @param <T> The type of a instant class that can be serialized. | ||
*/ | ||
public class AvroInstantSerializer<T extends Temporal> extends StdScalarSerializer<T> { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public static final AvroInstantSerializer<Instant> INSTANT = | ||
new AvroInstantSerializer<>(Instant.class, Function.identity()); | ||
|
||
public static final AvroInstantSerializer<OffsetDateTime> OFFSET_DATE_TIME = | ||
new AvroInstantSerializer<>(OffsetDateTime.class, OffsetDateTime::toInstant); | ||
|
||
public static final AvroInstantSerializer<ZonedDateTime> ZONED_DATE_TIME = | ||
new AvroInstantSerializer<>(ZonedDateTime.class, ZonedDateTime::toInstant); | ||
|
||
private final Function<T, Instant> getInstant; | ||
|
||
protected AvroInstantSerializer(Class<T> t, Function<T, Instant> getInstant) { | ||
super(t); | ||
this.getInstant = getInstant; | ||
} | ||
|
||
@Override | ||
public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { | ||
/** | ||
* Number of milliseconds, independent of a particular time zone or calendar, | ||
* from 1 January 1970 00:00:00.000 UTC. | ||
*/ | ||
final Instant instant = getInstant.apply(value); | ||
gen.writeNumber(instant.toEpochMilli()); | ||
} | ||
|
||
@Override | ||
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { | ||
JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint); | ||
if (v2 != null) { | ||
v2.numberType(JsonParser.NumberType.LONG); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.