Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Add explicit keep rules for RxJava `Result` types to prevent their generic information from being removed.
- Add `allowoptimization` flags for most kept types.
- Add `Invocation.annotationUrl` which returns the original URL from the method annotation.
- Add a converter for Jackson 3.

**Changed**

Expand Down
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ kotlinx-serialization = "1.10.0"
autoService = "1.1.1"
incap = "1.0.0"
jackson = "2.21.1"
jacksonAnnotations = "2.21"
jackson3 = "3.1.2"

[libraries]
androidPlugin = "com.android.tools.build:gradle:9.1.0"
Expand Down Expand Up @@ -63,8 +65,11 @@ rxjava3 = { module = "io.reactivex.rxjava3:rxjava", version = "3.1.12" }
reactiveStreams = { module = "org.reactivestreams:reactive-streams", version = "1.0.4" }
scalaLibrary = { module = "org.scala-lang:scala-library", version = "2.13.18" }
gson = { module = "com.google.code.gson:gson", version = "2.13.2" }
jacksonAnnotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jacksonAnnotations" }
jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jacksonDataformatCbor = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor", version.ref = "jackson" }
jackson3Databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson3" }
jackson3DataformatCbor = { module = "tools.jackson.dataformat:jackson-dataformat-cbor", version.ref = "jackson3" }
jaxbApi = { module = "javax.xml.bind:jaxb-api", version = "2.3.1" }
jaxbImpl = { module = "org.glassfish.jaxb:jaxb-runtime", version = "4.0.7" }
jaxb3Api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version = "3.0.1" }
Expand Down
1 change: 1 addition & 0 deletions retrofit-converters/jackson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
testImplementation libs.truth
testImplementation libs.okhttp.mockwebserver
testImplementation libs.testParameterInjector
testImplementation libs.jacksonAnnotations
testImplementation libs.jacksonDataformatCbor
}

Expand Down
34 changes: 34 additions & 0 deletions retrofit-converters/jackson3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Jackson Converter
=================

A `Converter` which uses [Jackson][1] 3 for serialization to and from JSON.

A default `ObjectMapper` instance will be created or one can be configured and passed to the
`JacksonConverterFactory` construction to further control the serialization.


Download
--------

Download [the latest JAR][2] or grab via [Maven][3]:
```xml
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>converter-jackson3</artifactId>
<version>latest.version</version>
</dependency>
```
or [Gradle][3]:
```groovy
implementation 'com.squareup.retrofit2:converter-jackson3:latest.version'
```

Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].



[1]: https://github.com/FasterXML/jackson
[2]: https://search.maven.org/remote_content?g=com.squareup.retrofit2&a=converter-jackson3&v=LATEST
[3]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.squareup.retrofit2%22%20a%3A%22converter-jackson3%22
[snap]: https://s01.oss.sonatype.org/content/repositories/snapshots/

27 changes: 27 additions & 0 deletions retrofit-converters/jackson3/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apply plugin: 'java-library'
apply plugin: 'com.vanniktech.maven.publish'

dependencies {
api projects.retrofit
api libs.jackson3Databind
compileOnly libs.findBugsAnnotations

testImplementation libs.junit
testImplementation libs.truth
testImplementation libs.okhttp.mockwebserver
testImplementation libs.testParameterInjector
testImplementation libs.jacksonAnnotations
testImplementation libs.jackson3DataformatCbor
}

jar {
manifest {
attributes 'Automatic-Module-Name': 'retrofit2.converter.jackson3'
}
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
3 changes: 3 additions & 0 deletions retrofit-converters/jackson3/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=converter-jackson3
POM_NAME=Converter: Jackson3
POM_DESCRIPTION=A Retrofit Converter which uses Jackson 3 for serialization.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* 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 retrofit2.converter.jackson3;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Converter;
import retrofit2.Retrofit;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectReader;
import tools.jackson.databind.ObjectWriter;

/**
* A {@linkplain Converter.Factory converter} which uses Jackson.
*
* <p>Because Jackson is so flexible in the types it supports, this converter assumes that it can
* handle all types. If you are mixing JSON serialization with something else (such as protocol
* buffers), you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this
* instance} last to allow the other converters a chance to see their types.
*/
public final class JacksonConverterFactory extends Converter.Factory {
private static final MediaType DEFAULT_MEDIA_TYPE =
MediaType.get("application/json; charset=UTF-8");

/** Create an instance using a default {@link ObjectMapper} instance for conversion. */
public static JacksonConverterFactory create() {
return new JacksonConverterFactory(new ObjectMapper(), DEFAULT_MEDIA_TYPE, false);
}

/** Create an instance using {@code mapper} for conversion. */
public static JacksonConverterFactory create(ObjectMapper mapper) {
return create(mapper, DEFAULT_MEDIA_TYPE);
}

/** Create an instance using {@code mapper} and {@code mediaType} for conversion. */
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static JacksonConverterFactory create(ObjectMapper mapper, MediaType mediaType) {
if (mapper == null) throw new NullPointerException("mapper == null");
if (mediaType == null) throw new NullPointerException("mediaType == null");
return new JacksonConverterFactory(mapper, mediaType, false);
}

private final ObjectMapper mapper;
private final MediaType mediaType;
private final boolean streaming;

private JacksonConverterFactory(ObjectMapper mapper, MediaType mediaType, boolean streaming) {
this.mapper = mapper;
this.mediaType = mediaType;
this.streaming = streaming;
}

/**
* Return a new factory which streams serialization of request messages to bytes on the HTTP thread
* This is either the calling thread for {@link Call#execute()}, or one of OkHttp's background
* threads for {@link Call#enqueue}. Response bytes are always converted to message instances on
* one of OkHttp's background threads.
*/
public JacksonConverterFactory withStreaming() {
return new JacksonConverterFactory(mapper, mediaType, true);
}

@Override
public Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
JavaType javaType = mapper.getTypeFactory().constructType(type);
ObjectReader reader = mapper.readerFor(javaType);
return new JacksonResponseBodyConverter<>(reader);
}

@Override
public Converter<?, RequestBody> requestBodyConverter(
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations,
Retrofit retrofit) {
JavaType javaType = mapper.getTypeFactory().constructType(type);
ObjectWriter writer = mapper.writerFor(javaType);
return new JacksonRequestBodyConverter<>(writer, mediaType, streaming);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* 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 retrofit2.converter.jackson3;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import retrofit2.Converter;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectWriter;

import java.io.IOException;

final class JacksonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private final ObjectWriter adapter;
private final MediaType mediaType;
private final boolean streaming;

JacksonRequestBodyConverter(ObjectWriter adapter, MediaType mediaType, boolean streaming) {
this.adapter = adapter;
this.mediaType = mediaType;
this.streaming = streaming;
}

@Override
public RequestBody convert(T value) throws IOException {
if (streaming) {
return new JacksonStreamingRequestBody(adapter, value, mediaType);
}

try {
byte[] bytes = adapter.writeValueAsBytes(value);
return RequestBody.create(bytes, mediaType);
} catch (JacksonException e) {
throw new IOException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* 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 retrofit2.converter.jackson3;

import okhttp3.ResponseBody;
import retrofit2.Converter;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectReader;

import java.io.IOException;

final class JacksonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final ObjectReader adapter;

JacksonResponseBodyConverter(ObjectReader adapter) {
this.adapter = adapter;
}

@Override
public T convert(ResponseBody value) throws IOException {
try (value) {
return adapter.readValue(value.byteStream());
} catch (JacksonException e) {
throw new IOException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2025 Square, Inc.
*
* 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 retrofit2.converter.jackson3;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectWriter;

import java.io.IOException;

final class JacksonStreamingRequestBody extends RequestBody {
private final ObjectWriter adapter;
private final Object value;
private final MediaType mediaType;

public JacksonStreamingRequestBody(ObjectWriter adapter, Object value, MediaType mediaType) {
this.adapter = adapter;
this.value = value;
this.mediaType = mediaType;
}

@Override
public MediaType contentType() {
return mediaType;
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
try {
adapter.writeValue(sink.outputStream(), value);
} catch (JacksonException e) {
throw new IOException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@retrofit2.internal.EverythingIsNonNull
package retrofit2.converter.jackson3;
Loading