diff --git a/README.md b/README.md
index 74c1fb280..55dda3597 100644
--- a/README.md
+++ b/README.md
@@ -409,6 +409,17 @@ public class Example {
For the lighter weight Jackson Jr, use `JacksonJrEncoder` and `JacksonJrDecoder` from
the [Jackson Jr Module](./jackson-jr).
+#### Moshi
+[Moshi](./moshi) includes an encoder and decoder you can use with a JSON API.
+Add `MoshiEncoder` and/or `MoshiDecoder` to your `Feign.Builder` like so:
+
+```java
+GitHub github = Feign.builder()
+ .encoder(new MoshiEncoder())
+ .decoder(new MoshiDecoder())
+ .target(GitHub.class, "https://api.github.com");
+```
+
#### Sax
[SaxDecoder](./sax) allows you to decode XML in a way that is compatible with normal JVM and also Android environments.
diff --git a/moshi/README.md b/moshi/README.md
new file mode 100644
index 000000000..1ffdb6fae
--- /dev/null
+++ b/moshi/README.md
@@ -0,0 +1,13 @@
+Moshi Codec
+===================
+
+This module adds support for encoding and decoding JSON via the Moshi library.
+
+Add `MoshiEncoder` and/or `MoshiDecoder` to your `Feign.Builder` like so:
+
+```java
+GitHub github = Feign.builder()
+ .encoder(new MoshiEncoder())
+ .decoder(new MoshiDecoder())
+ .target(GitHub.class, "https://api.github.com");
+```
diff --git a/moshi/pom.xml b/moshi/pom.xml
new file mode 100644
index 000000000..a97784d2c
--- /dev/null
+++ b/moshi/pom.xml
@@ -0,0 +1,58 @@
+
+
+
+ 4.0.0
+
+
+ io.github.openfeign
+ parent
+ 13.0-SNAPSHOT
+
+
+ feign-moshi
+ Feign Moshi
+ Feign Moshi
+
+
+ ${project.basedir}/..
+
+
+
+
+ ${project.groupId}
+ feign-core
+
+
+
+ com.squareup.moshi
+ moshi
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+ ${project.groupId}
+ feign-core
+ test-jar
+ test
+
+
+
diff --git a/moshi/src/main/java/feign/moshi/MoshiDecoder.java b/moshi/src/main/java/feign/moshi/MoshiDecoder.java
new file mode 100644
index 000000000..8baf101f2
--- /dev/null
+++ b/moshi/src/main/java/feign/moshi/MoshiDecoder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import static feign.Util.UTF_8;
+import static feign.Util.ensureClosed;
+
+import com.google.common.io.CharStreams;
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.JsonDataException;
+import com.squareup.moshi.Moshi;
+import feign.Response;
+import feign.Util;
+import feign.codec.Decoder;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.Type;
+
+public class MoshiDecoder implements Decoder {
+ private final Moshi moshi;
+
+ public MoshiDecoder(Moshi moshi) {
+ this.moshi = moshi;
+ }
+
+ public MoshiDecoder() {
+ this.moshi = new Moshi.Builder().build();
+ }
+
+ public MoshiDecoder(Iterable> adapters) {
+ this(MoshiFactory.create(adapters));
+ }
+
+ @Override
+ public Object decode(Response response, Type type) throws IOException {
+ JsonAdapter jsonAdapter = moshi.adapter(type);
+
+ if (response.status() == 404 || response.status() == 204) return Util.emptyValueOf(type);
+ if (response.body() == null) return null;
+
+ Reader reader = response.body().asReader(UTF_8);
+
+ try {
+ return parseJson(jsonAdapter, reader);
+ } catch (JsonDataException e) {
+ if (e.getCause() != null && e.getCause() instanceof IOException) {
+ throw (IOException) e.getCause();
+ }
+ throw e;
+ } finally {
+ ensureClosed(reader);
+ }
+ }
+
+ private Object parseJson(JsonAdapter jsonAdapter, Reader reader) throws IOException {
+ String targetString = CharStreams.toString(reader);
+ return jsonAdapter.fromJson(targetString);
+ }
+}
diff --git a/moshi/src/main/java/feign/moshi/MoshiEncoder.java b/moshi/src/main/java/feign/moshi/MoshiEncoder.java
new file mode 100644
index 000000000..0fb60c1b3
--- /dev/null
+++ b/moshi/src/main/java/feign/moshi/MoshiEncoder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.Moshi;
+import feign.RequestTemplate;
+import feign.codec.Encoder;
+import java.lang.reflect.Type;
+
+public class MoshiEncoder implements Encoder {
+
+ private final Moshi moshi;
+
+ public MoshiEncoder() {
+ this.moshi = new Moshi.Builder().build();
+ }
+
+ public MoshiEncoder(Moshi moshi) {
+ this.moshi = moshi;
+ }
+
+ public MoshiEncoder(Iterable> adapters) {
+ this(MoshiFactory.create(adapters));
+ }
+
+ @Override
+ public void encode(Object object, Type bodyType, RequestTemplate template) {
+ JsonAdapter jsonAdapter = moshi.adapter(bodyType).indent(" ");
+ template.body(jsonAdapter.toJson(object));
+ }
+}
diff --git a/moshi/src/main/java/feign/moshi/MoshiFactory.java b/moshi/src/main/java/feign/moshi/MoshiFactory.java
new file mode 100644
index 000000000..922021c6d
--- /dev/null
+++ b/moshi/src/main/java/feign/moshi/MoshiFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.Moshi;
+
+public class MoshiFactory {
+ private MoshiFactory() {}
+
+ /**
+ * Registers JsonAdapter by implicit type. Adds one to read numbers in a {@code Map} as Integers.
+ */
+ static Moshi create(Iterable> adapters) {
+ Moshi.Builder builder = new Moshi.Builder();
+
+ for (JsonAdapter> adapter : adapters) {
+ builder.add(adapter.getClass(), adapter);
+ }
+
+ return builder.build();
+ }
+}
diff --git a/moshi/src/test/java/feign/moshi/MoshiDecoderTest.java b/moshi/src/test/java/feign/moshi/MoshiDecoderTest.java
new file mode 100644
index 000000000..d2ba59ed3
--- /dev/null
+++ b/moshi/src/test/java/feign/moshi/MoshiDecoderTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import static feign.Util.UTF_8;
+import static feign.assertj.FeignAssertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.Moshi;
+import feign.Request;
+import feign.Response;
+import feign.Util;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.Test;
+
+public class MoshiDecoderTest {
+
+ @Test
+ public void decodes() throws Exception {
+
+ class Zone extends LinkedHashMap {
+
+ Zone(String name) {
+ this(name, null);
+ }
+
+ Zone(String name, String id) {
+ put("name", name);
+ if (id != null) {
+ put("id", id);
+ }
+ }
+
+ private static final long serialVersionUID = 1L;
+ }
+
+ List zones = new LinkedList<>();
+ zones.add(new Zone("denominator.io."));
+ zones.add(new Zone("denominator.io.", "ABCD"));
+
+ Response response =
+ Response.builder()
+ .status(200)
+ .reason("OK")
+ .headers(Collections.emptyMap())
+ .request(
+ Request.create(
+ Request.HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
+ .body(zonesJson, UTF_8)
+ .build();
+
+ assertEquals(zones, new MoshiDecoder().decode(response, List.class));
+ }
+
+ private String zonesJson =
+ "" //
+ + "[\n" //
+ + " {\n" //
+ + " \"name\": \"denominator.io.\"\n" //
+ + " },\n" //
+ + " {\n" //
+ + " \"name\": \"denominator.io.\",\n" //
+ + " \"id\": \"ABCD\"\n" //
+ + " }\n" //
+ + "]\n";
+
+ private final String videoGamesJson =
+ "{\n "
+ + " \"hero\": {\n "
+ + " \"enemy\": \"Bowser\",\n "
+ + " \"name\": \"Luigi\"\n "
+ + "},\n "
+ + "\"name\": \"Super Mario\"\n "
+ + "}";
+
+ @Test
+ public void nullBodyDecodesToNull() throws Exception {
+ Response response =
+ Response.builder()
+ .status(204)
+ .reason("OK")
+ .headers(Collections.emptyMap())
+ .request(
+ Request.create(
+ Request.HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
+ .build();
+ assertNull(new MoshiDecoder().decode(response, String.class));
+ }
+
+ @Test
+ public void emptyBodyDecodesToNull() throws Exception {
+ Response response =
+ Response.builder()
+ .status(204)
+ .reason("OK")
+ .headers(Collections.emptyMap())
+ .request(
+ Request.create(
+ Request.HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
+ .body(new byte[0])
+ .build();
+ assertNull(new MoshiDecoder().decode(response, String.class));
+ }
+
+ /** Enabled via {@link feign.Feign.Builder#dismiss404()} */
+ @Test
+ public void notFoundDecodesToEmpty() throws Exception {
+ Response response =
+ Response.builder()
+ .status(404)
+ .reason("NOT FOUND")
+ .headers(Collections.emptyMap())
+ .request(
+ Request.create(
+ Request.HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
+ .build();
+ assertThat((byte[]) new MoshiDecoder().decode(response, byte[].class)).isEmpty();
+ }
+
+ @Test
+ public void customDecoder() throws Exception {
+ final UpperZoneJSONAdapter upperZoneAdapter = new UpperZoneJSONAdapter();
+
+ MoshiDecoder decoder = new MoshiDecoder(Collections.singleton(upperZoneAdapter));
+
+ List zones = new LinkedList<>();
+ zones.add(new Zone("DENOMINATOR.IO."));
+ zones.add(new Zone("DENOMINATOR.IO.", "ABCD"));
+
+ Response response =
+ Response.builder()
+ .status(200)
+ .reason("OK")
+ .headers(Collections.emptyMap())
+ .request(
+ Request.create(
+ Request.HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
+ .body(zonesJson, UTF_8)
+ .build();
+
+ assertEquals(zones, decoder.decode(response, UpperZoneJSONAdapter.class));
+ }
+
+ @Test
+ public void customObjectDecoder() throws Exception {
+ final JsonAdapter videoGameJsonAdapter =
+ new Moshi.Builder().build().adapter(VideoGame.class);
+
+ MoshiDecoder decoder = new MoshiDecoder(Collections.singleton(videoGameJsonAdapter));
+
+ VideoGame videoGame = new VideoGame("Super Mario", "Luigi", "Bowser");
+
+ Response response =
+ Response.builder()
+ .status(200)
+ .reason("OK")
+ .headers(Collections.emptyMap())
+ .request(
+ Request.create(
+ Request.HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
+ .body(videoGamesJson, UTF_8)
+ .build();
+
+ VideoGame actual = (VideoGame) decoder.decode(response, videoGameJsonAdapter.getClass());
+
+ assertThat(actual).isEqualToComparingFieldByFieldRecursively(videoGame);
+ }
+}
diff --git a/moshi/src/test/java/feign/moshi/MoshiEncoderTest.java b/moshi/src/test/java/feign/moshi/MoshiEncoderTest.java
new file mode 100644
index 000000000..fe927caf4
--- /dev/null
+++ b/moshi/src/test/java/feign/moshi/MoshiEncoderTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import static feign.assertj.FeignAssertions.assertThat;
+
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.Moshi;
+import feign.RequestTemplate;
+import java.util.*;
+import org.junit.Test;
+
+public class MoshiEncoderTest {
+
+ @Test
+ public void encodesMapObjectNumericalValuesAsInteger() {
+ Map map = new LinkedHashMap<>();
+ map.put("foo", 1);
+
+ RequestTemplate template = new RequestTemplate();
+ new MoshiEncoder().encode(map, Map.class, template);
+
+ assertThat(template)
+ .hasBody(
+ "{\n" //
+ + " \"foo\": 1\n" //
+ + "}");
+ }
+
+ @Test
+ public void encodesFormParams() {
+
+ Map form = new LinkedHashMap<>();
+ form.put("foo", 1);
+ form.put("bar", Arrays.asList(2, 3));
+
+ RequestTemplate template = new RequestTemplate();
+
+ new MoshiEncoder().encode(form, Map.class, template);
+
+ assertThat(template)
+ .hasBody(
+ "{\n" //
+ + " \"foo\": 1,\n" //
+ + " \"bar\": [\n" //
+ + " 2,\n" //
+ + " 3\n" //
+ + " ]\n" //
+ + "}");
+ }
+
+ @Test
+ public void customEncoder() {
+ final UpperZoneJSONAdapter upperZoneAdapter = new UpperZoneJSONAdapter();
+
+ MoshiEncoder encoder = new MoshiEncoder(Collections.singleton(upperZoneAdapter));
+
+ List zones = new LinkedList<>();
+ zones.add(new Zone("denominator.io."));
+ zones.add(new Zone("denominator.io.", "abcd"));
+
+ RequestTemplate template = new RequestTemplate();
+ encoder.encode(zones, UpperZoneJSONAdapter.class, template);
+
+ assertThat(template)
+ .hasBody(
+ "" //
+ + "[\n" //
+ + " {\n" //
+ + " \"name\": \"DENOMINATOR.IO.\"\n" //
+ + " },\n" //
+ + " {\n" //
+ + " \"name\": \"DENOMINATOR.IO.\",\n" //
+ + " \"id\": \"ABCD\"\n" //
+ + " }\n" //
+ + "]");
+ }
+
+ @Test
+ public void customObjectEncoder() {
+ final JsonAdapter videoGameJsonAdapter =
+ new Moshi.Builder().build().adapter(VideoGame.class);
+ MoshiEncoder encoder = new MoshiEncoder(Collections.singleton(videoGameJsonAdapter));
+
+ VideoGame videoGame = new VideoGame("Super Mario", "Luigi", "Bowser");
+
+ RequestTemplate template = new RequestTemplate();
+ encoder.encode(videoGame, videoGameJsonAdapter.getClass(), template);
+
+ assertThat(template)
+ .hasBody(
+ "{\n"
+ + " \"hero\": {\n"
+ + " \"enemy\": \"Bowser\",\n"
+ + " \"name\": \"Luigi\"\n"
+ + " },\n"
+ + " \"name\": \"Super Mario\"\n"
+ + "}");
+ }
+}
diff --git a/moshi/src/test/java/feign/moshi/UpperZoneJSONAdapter.java b/moshi/src/test/java/feign/moshi/UpperZoneJSONAdapter.java
new file mode 100644
index 000000000..fae5c9f4f
--- /dev/null
+++ b/moshi/src/test/java/feign/moshi/UpperZoneJSONAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import com.squareup.moshi.*;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Map;
+
+class UpperZoneJSONAdapter extends JsonAdapter> {
+
+ @ToJson
+ public void toJson(JsonWriter out, LinkedList value) throws IOException {
+ out.beginArray();
+ for (Zone zone : value) {
+ out.beginObject();
+ for (Map.Entry entry : zone.entrySet()) {
+ out.name(entry.getKey()).value(entry.getValue().toString().toUpperCase());
+ }
+ out.endObject();
+ }
+ out.endArray();
+ }
+
+ @FromJson
+ public LinkedList fromJson(JsonReader in) throws IOException {
+ LinkedList zones = new LinkedList<>();
+ in.beginArray();
+ while (in.hasNext()) {
+ in.beginObject();
+ Zone zone = new Zone();
+ while (in.hasNext()) {
+ zone.put(in.nextName(), in.nextString().toUpperCase());
+ }
+ in.endObject();
+ zones.add(zone);
+ }
+ in.endArray();
+ return zones;
+ }
+}
diff --git a/moshi/src/test/java/feign/moshi/VideoGame.java b/moshi/src/test/java/feign/moshi/VideoGame.java
new file mode 100644
index 000000000..85eab13b9
--- /dev/null
+++ b/moshi/src/test/java/feign/moshi/VideoGame.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import com.squareup.moshi.Json;
+
+public class VideoGame {
+
+ @Json(name = "name")
+ public final String name;
+
+ @Json(name = "hero")
+ public final Hero hero;
+
+ public VideoGame(String name, String hero, String enemy) {
+ this.name = name;
+ this.hero = new Hero(hero, enemy);
+ }
+
+ static class Hero {
+ @Json(name = "name")
+ public final String name;
+
+ @Json(name = "enemy")
+ public final String enemyName;
+
+ Hero(String name, String enemyName) {
+ this.name = name;
+ this.enemyName = enemyName;
+ }
+ }
+}
diff --git a/moshi/src/test/java/feign/moshi/Zone.java b/moshi/src/test/java/feign/moshi/Zone.java
new file mode 100644
index 000000000..2d3bc0864
--- /dev/null
+++ b/moshi/src/test/java/feign/moshi/Zone.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi;
+
+import java.util.LinkedHashMap;
+
+public class Zone extends LinkedHashMap {
+
+ Zone() {
+ // for reflective instantiation.
+ }
+
+ Zone(String name) {
+ this(name, null);
+ }
+
+ Zone(String name, String id) {
+ put("name", name);
+ if (id != null) {
+ put("id", id);
+ }
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/moshi/src/test/java/feign/moshi/examples/GithubExample.java b/moshi/src/test/java/feign/moshi/examples/GithubExample.java
new file mode 100644
index 000000000..11e2a847b
--- /dev/null
+++ b/moshi/src/test/java/feign/moshi/examples/GithubExample.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012-2023 The Feign Authors
+ *
+ * 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 feign.moshi.examples;
+
+import feign.Feign;
+import feign.Param;
+import feign.RequestLine;
+import feign.moshi.MoshiDecoder;
+import feign.moshi.MoshiEncoder;
+import java.util.List;
+
+public class GithubExample {
+
+ public static void main(String... args) {
+ GitHub github =
+ Feign.builder()
+ .encoder(new MoshiEncoder())
+ .decoder(new MoshiDecoder())
+ .target(GitHub.class, "https://api.github.com");
+
+ System.out.println("Let's fetch and print a list of the contributors to this library.");
+ List contributors = github.contributors("netflix", "feign");
+ for (Contributor contributor : contributors) {
+ System.out.println(contributor.login + " (" + contributor.contributions + ")");
+ }
+ }
+
+ interface GitHub {
+
+ @RequestLine("GET /repos/{owner}/{repo}/contributors")
+ List contributors(@Param("owner") String owner, @Param("repo") String repo);
+ }
+
+ static class Contributor {
+
+ String login;
+ int contributions;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 205c27623..93749a405 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,7 @@
example-wikipedia
example-wikipedia-with-springboot
benchmark
+ moshi
@@ -86,6 +87,7 @@
32.1.2-jre
1.43.3
2.10.1
+ 1.15.0
2.0.9
20230618
@@ -320,6 +322,12 @@
${gson.version}
+
+ com.squareup.moshi
+ moshi
+ ${moshi.version}
+
+
org.assertj
assertj-core