Added jackson as supported serialization library for retrofit2 (#16853)

* added jackson as supported serialization library for retrofit2; resolves #7435

* Jackson support for retrofit2 library: Adjusted ApiClient to respectively include only gson or jackson specific conversion support depending on which serialization framework is selected

* Jackson support for retrofit2 library: Adjusted dependencies to respectively only include those necessary for gson or jackson depending on which serialization framework is selected

* reorder converter factory additions to have minimal change

* -: skipping gson-fire dependency when gson is not selected

* -: Jackson support for retrofit2 library: since useplayws implies jackson usage, only adding dependencies via useplayws that are specific to it

* Jackson support for retrofit2 library: fixed whitespace issue in generated api clients

* Jackson support for retrofit2 library: removed duplicated play26 dependency version property

* Jackson support for retrofit2 library: update to play26 example as that had gson dependencies but does not seem to use it and now fully relies on the jackson setting to control the respective dependencies; play version property and dependency changed place because the jackson dependencies are grouped together and the play26 ones are placed after

* Jackson support for retrofit2 library: adjusting dependencies also for gradle file

* -: moved jackson databind version out of jackson section since it is used independently of jackson support it seems

* -: update gradle dependencies to match changes in maven dependencies
This commit is contained in:
gf-smtzgr
2024-03-04 05:50:42 +01:00
committed by GitHub
parent 5d43c88540
commit 2d155105a4
10 changed files with 339 additions and 34 deletions

View File

@@ -606,8 +606,9 @@ public class JavaClientCodegen extends AbstractJavaCodegen
} else if (RETROFIT_2.equals(getLibrary())) {
supportingFiles.add(new SupportingFile("auth/OAuthOkHttpClient.mustache", authFolder, "OAuthOkHttpClient.java"));
supportingFiles.add(new SupportingFile("CollectionFormats.mustache", invokerFolder, "CollectionFormats.java"));
forceSerializationLibrary(SERIALIZATION_LIBRARY_GSON);
if (RETROFIT_2.equals(getLibrary()) && !usePlayWS) {
if (SERIALIZATION_LIBRARY_JACKSON.equals(getSerializationLibrary())) {
supportingFiles.add(new SupportingFile("JSON_jackson.mustache", invokerFolder, "JSON.java"));
} else if (!usePlayWS) {
supportingFiles.add(new SupportingFile("JSON.mustache", invokerFolder, "JSON.java"));
}
} else if (JERSEY2.equals(getLibrary())) {

View File

@@ -1,8 +1,10 @@
package {{invokerPackage}};
{{#gson}}
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.JsonElement;
{{/gson}}
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
@@ -22,7 +24,12 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
{{#useRxJava3}}
import hu.akarnokd.rxjava3.retrofit.RxJava3CallAdapterFactory;
{{/useRxJava3}}
{{#gson}}
import retrofit2.converter.gson.GsonConverterFactory;
{{/gson}}
{{#jackson}}
import retrofit2.converter.jackson.JacksonConverterFactory;
{{/jackson}}
import retrofit2.converter.scalars.ScalarsConverterFactory;
import {{invokerPackage}}.auth.HttpBasicAuth;
import {{invokerPackage}}.auth.HttpBearerAuth;
@@ -157,7 +164,12 @@ public class ApiClient {
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
{{/useRxJava3}}
.addConverterFactory(ScalarsConverterFactory.create())
{{#jackson}}
.addConverterFactory(JacksonConverterFactory.create(json.getMapper()));
{{/jackson}}
{{#gson}}
.addConverterFactory(GsonCustomConverterFactory.create(json.getGson()));
{{/gson}}
}
public <S> S createService(Class<S> serviceClass) {
@@ -173,6 +185,7 @@ public class ApiClient {
return this;
}
{{#gson}}
public ApiClient setSqlDateFormat(DateFormat dateFormat) {
this.json.setSqlDateFormat(dateFormat);
return this;
@@ -200,8 +213,9 @@ public class ApiClient {
this.json.setLocalDateFormat(dateFormat);
return this;
}
{{/jsr310}}
{{/gson}}
/**
* Helper method to configure the first api key found
@@ -401,6 +415,7 @@ public class ApiClient {
}
}
{{#gson}}
/**
* This wrapper is to take care of this case:
* when the deserialization fails due to JsonParseException and the
@@ -455,3 +470,4 @@ class GsonCustomConverterFactory extends Converter.Factory
return gsonConverterFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
}
{{/gson}}

View File

@@ -0,0 +1,261 @@
package {{invokerPackage}};
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
{{#openApiNullable}}
import org.openapitools.jackson.nullable.JsonNullableModule;
{{/openApiNullable}}
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
{{#joda}}
import com.fasterxml.jackson.datatype.joda.JodaModule;
{{/joda}}
{{#models.0}}
import {{modelPackage}}.*;
{{/models.0}}
import java.text.DateFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import {{javaxPackage}}.ws.rs.core.GenericType;
import {{javaxPackage}}.ws.rs.ext.ContextResolver;
{{>generatedAnnotation}}
public class JSON implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper;
public JSON() {
mapper = JsonMapper.builder()
.serializationInclusion(JsonInclude.Include.NON_NULL)
.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
.defaultDateFormat(new RFC3339DateFormat())
.addModule(new JavaTimeModule())
{{#joda}}
.addModule(new JodaModule())
{{/joda}}
{{#openApiNullable}}
.addModule(new JsonNullableModule())
{{/openApiNullable}}
.build();
}
/**
* Set the date format for JSON (de)serialization with Date properties.
* @param dateFormat Date format
*/
public void setDateFormat(DateFormat dateFormat) {
mapper.setDateFormat(dateFormat);
}
@Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
/**
* Get the object mapper
*
* @return object mapper
*/
public ObjectMapper getMapper() { return mapper; }
/**
* Returns the target model class that should be used to deserialize the input data.
* The discriminator mappings are used to determine the target model class.
*
* @param node The input data.
* @param modelClass The class that contains the discriminator mappings.
*/
public static Class<?> getClassForElement(JsonNode node, Class<?> modelClass) {
ClassDiscriminatorMapping cdm = modelDiscriminators.get(modelClass);
if (cdm != null) {
return cdm.getClassForElement(node, new HashSet<>());
}
return null;
}
/**
* Helper class to register the discriminator mappings.
*/
private static class ClassDiscriminatorMapping {
// The model class name.
Class<?> modelClass;
// The name of the discriminator property.
String discriminatorName;
// The discriminator mappings for a model class.
Map<String, Class<?>> discriminatorMappings;
// Constructs a new class discriminator.
ClassDiscriminatorMapping(Class<?> cls, String propertyName, Map<String, Class<?>> mappings) {
modelClass = cls;
discriminatorName = propertyName;
discriminatorMappings = new HashMap<>();
if (mappings != null) {
discriminatorMappings.putAll(mappings);
}
}
// Return the name of the discriminator property for this model class.
String getDiscriminatorPropertyName() {
return discriminatorName;
}
// Return the discriminator value or null if the discriminator is not
// present in the payload.
String getDiscriminatorValue(JsonNode node) {
// Determine the value of the discriminator property in the input data.
if (discriminatorName != null) {
// Get the value of the discriminator property, if present in the input payload.
node = node.get(discriminatorName);
if (node != null && node.isValueNode()) {
String discrValue = node.asText();
if (discrValue != null) {
return discrValue;
}
}
}
return null;
}
/**
* Returns the target model class that should be used to deserialize the input data.
* This function can be invoked for anyOf/oneOf composed models with discriminator mappings.
* The discriminator mappings are used to determine the target model class.
*
* @param node The input data.
* @param visitedClasses The set of classes that have already been visited.
*/
Class<?> getClassForElement(JsonNode node, Set<Class<?>> visitedClasses) {
if (visitedClasses.contains(modelClass)) {
// Class has already been visited.
return null;
}
// Determine the value of the discriminator property in the input data.
String discrValue = getDiscriminatorValue(node);
if (discrValue == null) {
return null;
}
Class<?> cls = discriminatorMappings.get(discrValue);
// It may not be sufficient to return this cls directly because that target class
// may itself be a composed schema, possibly with its own discriminator.
visitedClasses.add(modelClass);
for (Class<?> childClass : discriminatorMappings.values()) {
ClassDiscriminatorMapping childCdm = modelDiscriminators.get(childClass);
if (childCdm == null) {
continue;
}
if (!discriminatorName.equals(childCdm.discriminatorName)) {
discrValue = getDiscriminatorValue(node);
if (discrValue == null) {
continue;
}
}
if (childCdm != null) {
// Recursively traverse the discriminator mappings.
Class<?> childDiscr = childCdm.getClassForElement(node, visitedClasses);
if (childDiscr != null) {
return childDiscr;
}
}
}
return cls;
}
}
/**
* Returns true if inst is an instance of modelClass in the OpenAPI model hierarchy.
*
* The Java class hierarchy is not implemented the same way as the OpenAPI model hierarchy,
* so it's not possible to use the instanceof keyword.
*
* @param modelClass A OpenAPI model class.
* @param inst The instance object.
*/
public static boolean isInstanceOf(Class<?> modelClass, Object inst, Set<Class<?>> visitedClasses) {
if (modelClass.isInstance(inst)) {
// This handles the 'allOf' use case with single parent inheritance.
return true;
}
if (visitedClasses.contains(modelClass)) {
// This is to prevent infinite recursion when the composed schemas have
// a circular dependency.
return false;
}
visitedClasses.add(modelClass);
// Traverse the oneOf/anyOf composed schemas.
Map<String, GenericType> descendants = modelDescendants.get(modelClass);
if (descendants != null) {
for (GenericType childType : descendants.values()) {
if (isInstanceOf(childType.getRawType(), inst, visitedClasses)) {
return true;
}
}
}
return false;
}
/**
* A map of discriminators for all model classes.
*/
private static Map<Class<?>, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<>();
/**
* A map of oneOf/anyOf descendants for each model class.
*/
private static Map<Class<?>, Map<String, GenericType>> modelDescendants = new HashMap<>();
/**
* Register a model class discriminator.
*
* @param modelClass the model class
* @param discriminatorPropertyName the name of the discriminator property
* @param mappings a map with the discriminator mappings.
*/
public static void registerDiscriminator(Class<?> modelClass, String discriminatorPropertyName, Map<String, Class<?>> mappings) {
ClassDiscriminatorMapping m = new ClassDiscriminatorMapping(modelClass, discriminatorPropertyName, mappings);
modelDiscriminators.put(modelClass, m);
}
/**
* Register the oneOf/anyOf descendants of the modelClass.
*
* @param modelClass the model class
* @param descendants a map of oneOf/anyOf descendants.
*/
public static void registerDescendants(Class<?> modelClass, Map<String, GenericType> descendants) {
modelDescendants.put(modelClass, descendants);
}
private static JSON json;
static
{
json = new JSON();
}
/**
* Get the default JSON instance.
*
* @return the default JSON instance
*/
public static JSON getDefault() {
return json;
}
/**
* Set the default JSON instance.
*
* @param json JSON instance to be used
*/
public static void setDefault(JSON json) {
JSON.json = json;
}
}

View File

@@ -99,12 +99,15 @@ if(hasProperty('target') && target == 'android') {
ext {
oltu_version = "1.0.1"
retrofit_version = "2.3.0"
{{#usePlayWS}}
jackson_version = "2.15.2"
jackson_databind_version = "2.15.2"
{{#jackson}}
jackson_version = "2.15.2"
javax_ws_rs_api_version = "2.1.1"
{{#openApiNullable}}
jackson_databind_nullable_version = "0.2.6"
{{/openApiNullable}}
{{/jackson}}
{{#usePlayWS}}
play_version = "2.6.7"
{{/usePlayWS}}
jakarta_annotation_version = "1.3.5"
@@ -145,16 +148,19 @@ dependencies {
{{/joda}}
{{#usePlayWS}}
implementation "com.typesafe.play:play-ahc-ws_2.12:$play_version"
{{/usePlayWS}}
{{#jackson}}
implementation "jakarta.validation:jakarta.validation-api:2.0.2"
implementation "com.squareup.retrofit2:converter-jackson:$retrofit_version"
implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version"
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "javax.ws.rs:javax.ws.rs-api:$javax_ws_rs_api_version"
{{#openApiNullable}}
implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version"
{{/openApiNullable}}
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
{{/usePlayWS}}
{{/jackson}}
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
testImplementation "junit:junit:$junit_version"
}

View File

@@ -222,11 +222,13 @@
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
{{#gson}}
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-gson</artifactId>
<version>${retrofit-version}</version>
</dependency>
{{/gson}}
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
@@ -248,11 +250,13 @@
</exclusion>
</exclusions>
</dependency>
{{#gson}}
<dependency>
<groupId>io.gsonfire</groupId>
<artifactId>gson-fire</artifactId>
<version>${gson-fire-version}</version>
</dependency>
{{/gson}}
{{#joda}}
<dependency>
<groupId>joda-time</groupId>
@@ -284,7 +288,7 @@
<version>3.0.0</version>
</dependency>
{{/useRxJava3}}
{{#usePlayWS}}
{{#jackson}}
<!-- JSON processing: jackson -->
<dependency>
<groupId>com.squareup.retrofit2</groupId>
@@ -321,16 +325,25 @@
<version>${jackson-version}</version>
</dependency>
{{/withXml}}
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-ahc-ws_2.12</artifactId>
<version>${play-version}</version>
</dependency>
{{#useBeanValidation}}
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${beanvalidation-version}</version>
</dependency>
{{/useBeanValidation}}
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${javax.ws.rs-api-version}</version>
</dependency>
{{/jackson}}
{{#usePlayWS}}
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-ahc-ws_2.12</artifactId>
<version>${play-version}</version>
</dependency>
{{/usePlayWS}}
{{#parcelableModel}}
<!-- Needed for Parcelable support-->
@@ -360,15 +373,20 @@
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
{{#gson}}
<gson-fire-version>1.9.0</gson-fire-version>
{{/gson}}
<swagger-annotations-version>1.6.3</swagger-annotations-version>
<jackson-databind-version>2.15.2</jackson-databind-version>
{{#usePlayWS}}
{{#jackson}}
<jackson-version>2.15.2</jackson-version>
<play-version>2.6.7</play-version>
{{#openApiNullable}}
<jackson-databind-nullable-version>0.2.6</jackson-databind-nullable-version>
{{/openApiNullable}}
<javax.ws.rs-api-version>2.1.1</javax.ws.rs-api-version>
{{/jackson}}
{{#usePlayWS}}
<play-version>2.6.7</play-version>
{{/usePlayWS}}
<retrofit-version>2.5.0</retrofit-version>
{{#useRxJava2}}

View File

@@ -99,8 +99,9 @@ if(hasProperty('target') && target == 'android') {
ext {
oltu_version = "1.0.1"
retrofit_version = "2.3.0"
jackson_version = "2.15.2"
jackson_databind_version = "2.15.2"
jackson_version = "2.15.2"
javax_ws_rs_api_version = "2.1.1"
jackson_databind_nullable_version = "0.2.6"
play_version = "2.6.7"
jakarta_annotation_version = "1.3.5"
@@ -124,9 +125,10 @@ dependencies {
implementation "com.squareup.retrofit2:converter-jackson:$retrofit_version"
implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version"
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "javax.ws.rs:javax.ws.rs-api:$javax_ws_rs_api_version"
implementation "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
testImplementation "junit:junit:$junit_version"
}

View File

@@ -215,11 +215,6 @@
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-gson</artifactId>
<version>${retrofit-version}</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
@@ -240,11 +235,6 @@
<artifactId>common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.gsonfire</groupId>
<artifactId>gson-fire</artifactId>
<version>${gson-fire-version}</version>
</dependency>
<!-- JSON processing: jackson -->
<dependency>
@@ -272,16 +262,21 @@
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-ahc-ws_2.12</artifactId>
<version>${play-version}</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${beanvalidation-version}</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>${javax.ws.rs-api-version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-ahc-ws_2.12</artifactId>
<version>${play-version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
@@ -301,12 +296,12 @@
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<gson-fire-version>1.9.0</gson-fire-version>
<swagger-annotations-version>1.6.3</swagger-annotations-version>
<jackson-databind-version>2.15.2</jackson-databind-version>
<jackson-version>2.15.2</jackson-version>
<play-version>2.6.7</play-version>
<jackson-databind-nullable-version>0.2.6</jackson-databind-nullable-version>
<javax.ws.rs-api-version>2.1.1</javax.ws.rs-api-version>
<play-version>2.6.7</play-version>
<retrofit-version>2.5.0</retrofit-version>
<jakarta-annotation-version>1.3.5</jakarta-annotation-version>
<beanvalidation-version>2.0.2</beanvalidation-version>

View File

@@ -99,6 +99,7 @@ if(hasProperty('target') && target == 'android') {
ext {
oltu_version = "1.0.1"
retrofit_version = "2.3.0"
jackson_databind_version = "2.15.2"
jakarta_annotation_version = "1.3.5"
swagger_annotations_version = "1.5.22"
junit_version = "4.13.2"
@@ -115,6 +116,7 @@ dependencies {
exclude group:'org.apache.oltu.oauth2' , module: 'org.apache.oltu.oauth2.common'
}
implementation "io.gsonfire:gson-fire:$json_fire_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
testImplementation "junit:junit:$junit_version"
}

View File

@@ -99,6 +99,7 @@ if(hasProperty('target') && target == 'android') {
ext {
oltu_version = "1.0.1"
retrofit_version = "2.3.0"
jackson_databind_version = "2.15.2"
jakarta_annotation_version = "1.3.5"
swagger_annotations_version = "1.5.22"
junit_version = "4.13.2"
@@ -118,6 +119,7 @@ dependencies {
exclude group:'org.apache.oltu.oauth2' , module: 'org.apache.oltu.oauth2.common'
}
implementation "io.gsonfire:gson-fire:$json_fire_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
testImplementation "junit:junit:$junit_version"
}

View File

@@ -99,6 +99,7 @@ if(hasProperty('target') && target == 'android') {
ext {
oltu_version = "1.0.1"
retrofit_version = "2.3.0"
jackson_databind_version = "2.15.2"
jakarta_annotation_version = "1.3.5"
swagger_annotations_version = "1.5.22"
junit_version = "4.13.2"
@@ -118,6 +119,7 @@ dependencies {
exclude group:'org.apache.oltu.oauth2' , module: 'org.apache.oltu.oauth2.common'
}
implementation "io.gsonfire:gson-fire:$json_fire_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
testImplementation "junit:junit:$junit_version"
}