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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- First-party converters now support deferring serialization to happen when the request body is written (i.e., during HTTP execution) rather than when the HTTP request is created. In some cases this moves conversion from a calling thread to a background thread, such as in the case when using `Call.enqueue` directly.

The following converters support this feature through a new `withStreaming()` factory method:
- Gson
- Moshi
- Wire

Expand All @@ -16,7 +17,7 @@

**Fixed**

- Nothing yet!
- Primitive types used with `@Tag` now work by storing the value boxed with the boxed class as the key.


## [2.11.0] - 2024-03-28
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ subprojects {

tasks.withType(JavaCompile).configureEach { task ->
task.options.errorprone {
excludedPaths = '.*/build/generated/source/proto/.*'
excludedPaths = '.*/build/generated/sources/proto/.*'
check('MissingFail', CheckSeverity.ERROR)
check('MissingOverride', CheckSeverity.ERROR)
check('UnusedException', CheckSeverity.ERROR)
Expand Down
16 changes: 8 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@
# limitations under the License.

[versions]
kotlin = "2.1.10"
kotlin = "2.1.20"
okhttp = "3.14.9"
protobuf = "3.25.6"
robovm = "2.3.14"
kotlinx-serialization = "1.8.0"
kotlinx-serialization = "1.8.1"
autoService = "1.1.1"
incap = "1.0.0"
jackson = "2.18.3"

[libraries]
androidPlugin = { module = "com.android.tools.build:gradle", version = "8.9.0" }
androidPlugin = { module = "com.android.tools.build:gradle", version = "8.9.1" }
robovmPlugin = { module = "com.mobidevelop.robovm:robovm-gradle-plugin", version.ref = "robovm" }
dokkaPlugin = "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0"
gradleMavenPublishPlugin = "com.vanniktech:gradle-maven-publish-plugin:0.31.0"
spotlessPlugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.2"
spotlessPlugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.3"

kotlin-stdLib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
Expand All @@ -40,7 +40,7 @@ errorproneJavac = { module = "com.google.errorprone:javac", version = "9+181-r41
animalSnifferPlugin = "ru.vyarus:gradle-animalsniffer-plugin:2.0.0"
animalSnifferAnnotations = { module = "org.codehaus.mojo:animal-sniffer-annotations", version = "1.24" }

protobufPlugin = "com.google.protobuf:protobuf-gradle-plugin:0.9.4"
protobufPlugin = "com.google.protobuf:protobuf-gradle-plugin:0.9.5"
protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }

Expand All @@ -50,7 +50,7 @@ incap-processor = { module = "net.ltgt.gradle.incap:incap-processor", version.re
autoService-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoService" }
autoService-compiler = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }

kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.1" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
kotlinx-serialization-proto = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
Expand All @@ -59,7 +59,7 @@ okhttp-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor
okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
junit = { module = "junit:junit", version = "4.13.2" }
truth = "com.google.truth:truth:1.4.4"
guava = { module = "com.google.guava:guava", version = "33.4.0-jre" }
guava = { module = "com.google.guava:guava", version = "33.4.7-jre" }
android = { module = "com.google.android:android", version = "4.1.1.4" }
findBugsAnnotations = { module = "com.google.code.findbugs:jsr305", version = "3.0.2" }
androidxTestRunner = { module = "androidx.test:runner", version = "1.4.0" }
Expand All @@ -80,7 +80,7 @@ simpleXml = { module = "org.simpleframework:simple-xml", version = "2.7.1" }
wireRuntime = { module = "com.squareup.wire:wire-runtime", version = "2.2.0" }
jsoup = { module = "org.jsoup:jsoup", version = "1.19.1" }
robovm = { module = "com.mobidevelop.robovm:robovm-rt", version.ref = "robovm" }
googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.25.2"
googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.26.0"
ktlint = "com.pinterest.ktlint:ktlint-cli:1.5.0"
compileTesting = "com.google.testing.compile:compile-testing:0.21.0"
testParameterInjector = "com.google.testparameterinjector:test-parameter-injector:1.18"
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private static final class CallCancelListenableFuture<T> extends AbstractFuture<
}

@Override
public boolean set(@org.checkerframework.checker.nullness.qual.Nullable T value) {
public boolean set(T value) {
return super.set(value);
}

Expand Down
1 change: 1 addition & 0 deletions retrofit-converters/gson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies {
testImplementation libs.junit
testImplementation libs.truth
testImplementation libs.okhttp.mockwebserver
testImplementation libs.testParameterInjector
}

jar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.lang.reflect.Type;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Converter;
import retrofit2.Retrofit;

Expand Down Expand Up @@ -49,13 +50,25 @@ public static GsonConverterFactory create() {
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static GsonConverterFactory create(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
return new GsonConverterFactory(gson);
return new GsonConverterFactory(gson, false);
}

private final Gson gson;
private final boolean streaming;

private GsonConverterFactory(Gson gson) {
private GsonConverterFactory(Gson gson, boolean streaming) {
this.gson = gson;
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 GsonConverterFactory withStreaming() {
return new GsonConverterFactory(gson, true);
}

@Override
Expand All @@ -72,6 +85,6 @@ public Converter<?, RequestBody> requestBodyConverter(
Annotation[] methodAnnotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
return new GsonRequestBodyConverter<>(gson, adapter, streaming);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,38 @@
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import retrofit2.Converter;

final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");

private final Gson gson;
private final TypeAdapter<T> adapter;
private final boolean streaming;

GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter, boolean streaming) {
this.gson = gson;
this.adapter = adapter;
this.streaming = streaming;
}

@Override
public RequestBody convert(T value) throws IOException {
if (streaming) {
return new GsonStreamingRequestBody<>(gson, adapter, value);
}

Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
writeJson(buffer, gson, adapter, value);
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}

static <T> void writeJson(BufferedSink sink, Gson gson, TypeAdapter<T> adapter, T value)
throws IOException {
Writer writer = new OutputStreamWriter(sink.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.gson;

import static retrofit2.converter.gson.GsonRequestBodyConverter.MEDIA_TYPE;
import static retrofit2.converter.gson.GsonRequestBodyConverter.writeJson;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;

final class GsonStreamingRequestBody<T> extends RequestBody {
private final Gson gson;
private final TypeAdapter<T> adapter;
private final T value;

public GsonStreamingRequestBody(Gson gson, TypeAdapter<T> adapter, T value) {
this.gson = gson;
this.adapter = adapter;
this.value = value;
}

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

@Override
public void writeTo(BufferedSink sink) throws IOException {
writeJson(sink, gson, adapter, value);
}
}
Loading