Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#19921] Add RFC 3339 compatible Jackson module for java.time types #19925

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,8 @@ public void processOpts() {
additionalProperties.remove(SERIALIZATION_LIBRARY_GSON);
additionalProperties.remove(SERIALIZATION_LIBRARY_JSONB);
supportingFiles.add(new SupportingFile("RFC3339DateFormat.mustache", invokerFolder, "RFC3339DateFormat.java"));
supportingFiles.add(new SupportingFile("RFC3339InstantDeserializer.mustache", invokerFolder, "RFC3339InstantDeserializer.java"));
supportingFiles.add(new SupportingFile("RFC3339JavaTimeModule.mustache", invokerFolder, "RFC3339JavaTimeModule.java"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional, not required for PR approval imo: Currently these supporting files are added even if the chosen library doesn't use them at all. You could introduce an additional if here to only add the files where they are actually supported.

break;
case SERIALIZATION_LIBRARY_GSON:
additionalProperties.put(SERIALIZATION_LIBRARY_GSON, "true");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{{>licenseInfo}}
package {{invokerPackage}};

import java.io.IOException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.function.BiFunction;
import java.util.function.Function;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;

{{>generatedAnnotation}}
public class RFC3339InstantDeserializer<T extends Temporal> extends InstantDeserializer<T> {

public static final RFC3339InstantDeserializer<Instant> INSTANT = new RFC3339InstantDeserializer<>(
Instant.class, DateTimeFormatter.ISO_INSTANT,
Instant::from,
a -> Instant.ofEpochMilli( a.value ),
a -> Instant.ofEpochSecond( a.integer, a.fraction ),
null,
true // yes, replace zero offset with Z
);

public static final RFC3339InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new RFC3339InstantDeserializer<>(
OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
OffsetDateTime::from,
a -> OffsetDateTime.ofInstant( Instant.ofEpochMilli( a.value ), a.zoneId ),
a -> OffsetDateTime.ofInstant( Instant.ofEpochSecond( a.integer, a.fraction ), a.zoneId ),
(d, z) -> ( d.isEqual( OffsetDateTime.MIN ) || d.isEqual( OffsetDateTime.MAX ) ?
d :
d.withOffsetSameInstant( z.getRules().getOffset( d.toLocalDateTime() ) ) ),
true // yes, replace zero offset with Z
);

public static final RFC3339InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new RFC3339InstantDeserializer<>(
ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
ZonedDateTime::from,
a -> ZonedDateTime.ofInstant( Instant.ofEpochMilli( a.value ), a.zoneId ),
a -> ZonedDateTime.ofInstant( Instant.ofEpochSecond( a.integer, a.fraction ), a.zoneId ),
ZonedDateTime::withZoneSameInstant,
false // keep zero offset and Z separate since zones explicitly supported
);

protected RFC3339InstantDeserializer(
Class<T> supportedType,
DateTimeFormatter formatter,
Function<TemporalAccessor, T> parsedToValue,
Function<FromIntegerArguments, T> fromMilliseconds,
Function<FromDecimalArguments, T> fromNanoseconds,
BiFunction<T, ZoneId, T> adjust,
boolean replaceZeroOffsetAsZ) {
super(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This super constructor is deprecated (see here). Could you please switch to a different one? I guess you could simply use the one recommended in the above link. And for the additional parameters required there, you could use the default values. (DEFAULT_NORMALIZE_ZONE_ID=true, DEFAULT_ALWAYS_ALLOW_STRINGIFIED_DATE_TIMESTAMPS=false)

supportedType,
formatter,
parsedToValue,
fromMilliseconds,
fromNanoseconds,
adjust,
replaceZeroOffsetAsZ
);
}

@Override
protected T _fromString(JsonParser p, DeserializationContext ctxt, String string0) throws IOException {
return super._fromString(p, ctxt, string0.replace( ' ', 'T' ));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{>licenseInfo}}
package {{invokerPackage}};

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;

import com.fasterxml.jackson.databind.module.SimpleModule;

{{>generatedAnnotation}}
public class RFC3339JavaTimeModule extends SimpleModule {

public RFC3339JavaTimeModule() {
super("RFC3339JavaTimeModule");

addDeserializer(Instant.class, RFC3339InstantDeserializer.INSTANT);
addDeserializer(OffsetDateTime.class, RFC3339InstantDeserializer.OFFSET_DATE_TIME);
addDeserializer(ZonedDateTime.class, RFC3339InstantDeserializer.ZONED_DATE_TIME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
{{#openApiNullable}}
objectMapper.registerModule(new JsonNullableModule());
{{/openApiNullable}}
objectMapper.registerModule(new RFC3339JavaTimeModule());
objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat());

dateFormat = ApiClient.buildDefaultDateFormat();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ public class ApiClient {
{{/joda}}
objectMapper.registerModule(new JavaTimeModule());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JavaTimeModule can already deserialize RFC3339 datetimes

{{#openApiNullable}}
objectMapper.registerModule(new RFC3339JavaTimeModule());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this new line shouldn't be inside the {{#openApiNullable}} block, right?

JsonNullableModule jnm = new JsonNullableModule();
objectMapper.registerModule(jnm);
{{/openApiNullable}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class JSON implements ContextResolver<ObjectMapper> {
{{#openApiNullable}}
.addModule(new JsonNullableModule())
{{/openApiNullable}}
.addModule(new RFC3339JavaTimeModule())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class JSON implements ContextResolver<ObjectMapper> {
{{#openApiNullable}}
.addModule(new JsonNullableModule())
{{/openApiNullable}}
.addModule(new RFC3339JavaTimeModule())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ public class ApiClient {
{{#openApiNullable}}
mapper.registerModule(new JsonNullableModule());
{{/openApiNullable}}
mapper.registerModule(new RFC3339JavaTimeModule());
return mapper;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ public void testSupportedSecuritySchemesJersey() {

List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();

assertThat(files).hasSize(32);
assertThat(files).hasSize(34);
validateJavaSourceFiles(files);
assertThat(output.resolve("src/main/java/xyz/abcdef/api/DefaultApi.java")).content().contains(
"public class DefaultApi",
Expand Down Expand Up @@ -586,7 +586,7 @@ public void testSupportedSecuritySchemesJersey() {

List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();

assertThat(files).hasSize(35);
assertThat(files).hasSize(37);

validateJavaSourceFiles(files);
assertThat(output.resolve("src/main/java/xyz/abcdef/api/PingApi.java")).content().contains(
Expand Down Expand Up @@ -1389,7 +1389,7 @@ public void testRestTemplateWithFreeFormInQueryParameters() {
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();

validateJavaSourceFiles(files);
assertThat(files).hasSize(35);
assertThat(files).hasSize(37);
TestUtils.assertFileContains(output.resolve("src/main/java/xyz/abcdef/ApiClient.java"),
"public static String urlEncode(String s) { return URLEncoder.encode(s,"
+ " UTF_8).replaceAll(\"\\\\+\", \"%20\"); }"
Expand All @@ -1411,7 +1411,7 @@ public void testRestTemplateWithFreeFormInQueryParameters() {
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();

validateJavaSourceFiles(files);
assertThat(files).hasSize(38);
assertThat(files).hasSize(40);
assertThat(output.resolve("src/main/java/xyz/abcdef/api/DefaultApi.java")).content()
.contains(
"localVarQueryParams.addAll(ApiClient.parameterToPairs(\"since\", queryObject.getSince()));",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void testApacheHttpClientExplodedQueryParamObject() throws IOException {
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(clientOptInput).generate();

Assert.assertEquals(files.size(), 42);
Assert.assertEquals(files.size(), 44);
validateJavaSourceFiles(files);

TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/api/DefaultApi.java"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ src/main/java/org/openapitools/client/Configuration.java
src/main/java/org/openapitools/client/JavaTimeFormatter.java
src/main/java/org/openapitools/client/Pair.java
src/main/java/org/openapitools/client/RFC3339DateFormat.java
src/main/java/org/openapitools/client/RFC3339InstantDeserializer.java
src/main/java/org/openapitools/client/RFC3339JavaTimeModule.java
src/main/java/org/openapitools/client/ServerConfiguration.java
src/main/java/org/openapitools/client/ServerVariable.java
src/main/java/org/openapitools/client/StringUtil.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public ApiClient(CloseableHttpClient httpClient) {
objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.registerModule(new JsonNullableModule());
objectMapper.registerModule(new RFC3339JavaTimeModule());
objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat());

dateFormat = ApiClient.buildDefaultDateFormat();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Echo Server API
* Echo Server API
*
* The version of the OpenAPI document: 0.1.0
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package org.openapitools.client;

import java.io.IOException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.function.BiFunction;
import java.util.function.Function;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.10.0-SNAPSHOT")
public class RFC3339InstantDeserializer<T extends Temporal> extends InstantDeserializer<T> {

public static final RFC3339InstantDeserializer<Instant> INSTANT = new RFC3339InstantDeserializer<>(
Instant.class, DateTimeFormatter.ISO_INSTANT,
Instant::from,
a -> Instant.ofEpochMilli( a.value ),
a -> Instant.ofEpochSecond( a.integer, a.fraction ),
null,
true // yes, replace zero offset with Z
);

public static final RFC3339InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new RFC3339InstantDeserializer<>(
OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
OffsetDateTime::from,
a -> OffsetDateTime.ofInstant( Instant.ofEpochMilli( a.value ), a.zoneId ),
a -> OffsetDateTime.ofInstant( Instant.ofEpochSecond( a.integer, a.fraction ), a.zoneId ),
(d, z) -> ( d.isEqual( OffsetDateTime.MIN ) || d.isEqual( OffsetDateTime.MAX ) ?
d :
d.withOffsetSameInstant( z.getRules().getOffset( d.toLocalDateTime() ) ) ),
true // yes, replace zero offset with Z
);

public static final RFC3339InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new RFC3339InstantDeserializer<>(
ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
ZonedDateTime::from,
a -> ZonedDateTime.ofInstant( Instant.ofEpochMilli( a.value ), a.zoneId ),
a -> ZonedDateTime.ofInstant( Instant.ofEpochSecond( a.integer, a.fraction ), a.zoneId ),
ZonedDateTime::withZoneSameInstant,
false // keep zero offset and Z separate since zones explicitly supported
);

protected RFC3339InstantDeserializer(
Class<T> supportedType,
DateTimeFormatter formatter,
Function<TemporalAccessor, T> parsedToValue,
Function<FromIntegerArguments, T> fromMilliseconds,
Function<FromDecimalArguments, T> fromNanoseconds,
BiFunction<T, ZoneId, T> adjust,
boolean replaceZeroOffsetAsZ) {
super(
supportedType,
formatter,
parsedToValue,
fromMilliseconds,
fromNanoseconds,
adjust,
replaceZeroOffsetAsZ
);
}

@Override
protected T _fromString(JsonParser p, DeserializationContext ctxt, String string0) throws IOException {
return super._fromString(p, ctxt, string0.replace( ' ', 'T' ));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Echo Server API
* Echo Server API
*
* The version of the OpenAPI document: 0.1.0
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package org.openapitools.client;

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;

import com.fasterxml.jackson.databind.module.SimpleModule;

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.10.0-SNAPSHOT")
public class RFC3339JavaTimeModule extends SimpleModule {

public RFC3339JavaTimeModule() {
super("RFC3339JavaTimeModule");

addDeserializer(Instant.class, RFC3339InstantDeserializer.INSTANT);
addDeserializer(OffsetDateTime.class, RFC3339InstantDeserializer.OFFSET_DATE_TIME);
addDeserializer(ZonedDateTime.class, RFC3339InstantDeserializer.ZONED_DATE_TIME);
}
}
2 changes: 2 additions & 0 deletions samples/client/echo_api/java/native/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ src/main/java/org/openapitools/client/Configuration.java
src/main/java/org/openapitools/client/JSON.java
src/main/java/org/openapitools/client/Pair.java
src/main/java/org/openapitools/client/RFC3339DateFormat.java
src/main/java/org/openapitools/client/RFC3339InstantDeserializer.java
src/main/java/org/openapitools/client/RFC3339JavaTimeModule.java
src/main/java/org/openapitools/client/ServerConfiguration.java
src/main/java/org/openapitools/client/ServerVariable.java
src/main/java/org/openapitools/client/api/AuthApi.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ protected ObjectMapper createDefaultObjectMapper() {
mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new JsonNullableModule());
mapper.registerModule(new RFC3339JavaTimeModule());
return mapper;
}

Expand Down
Loading
Loading