Repository: square/retrofit
Issue: Add first-party LoganSquare converter.

I had in mind to build a LoganSquare converter, but checking if you had already done it, I have seen that you are working on changes for next release, talking about the converters part. I was wondering if you have already a fixed date for the update or you have to deal with many other milestones before releasing. I wait for your feedback. Thanks a lot


Issue comments:
Go for it.

I will try to find some time to work on this over weekend. Keep you updated

Found [that](https://gist.github.com/aurae/8427b93b27483763d9cb)  at https://github.com/bluelinelabs/LoganSquare/issues/5 

Looks good so far and works for me

I already saw this @sockeqwe , but the thing is that it works for the current version of retrofit, but it won't work for next version, as some base in the converters has changed

Hi guys, sorry for delaying in the response. I am really busy lately and I don't know when I will be ready to develop this feature, so if anyone is interested in working on this, do it. In case this is not something urgent and nobody is interested on developing this converter, I will do it when I find some time. Thanks

It's not urgent. Next significant version won't ship for at least two
months.

On Mon, Apr 6, 2015 at 2:20 PM Jonathan Fragoso Pérez <
notifications@github.com> wrote:

> Hi guys, sorry for delaying in the response. I am really busy lately and I
> don't know when I will be ready to develop this feature, so if anyone is
> interested in working on this, do it. In case this is not something urgent
> and nobody is interested on developing this converter, I will do it when I
> find some time. Thanks
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/square/retrofit/issues/762#issuecomment-90183333.

Alright, in this case I will try to find some time to work on this during next week. Thanks @JakeWharton 

Proposed in Pull Request #988 

We aren't going to be adding any more JSON converters. Copying a response I put on another PR for explanation:

Retrofit already offers 3 first-party JSON converters. Gson and Jackson are the two leading serializers for which we offer converters, and we include Moshi just because it's a Square library. Similarly, we have two first-party protocol buffer converters, one using the official library and one with Wire because it's a Square library. We have a single XML converter because... well no one really knows.

There are many more eclectic and specialized JSON converters just as there are for other serialization formats. Maintaining a bunch of these is burdensome as we have to track the APIs and changes in 6 external libraries which have nothing to do with Retrofit's core. I can think of at least 5 other JSON libraries that have popped up recently almost always just being built on top of Jackson or Gson.

Retrofit was modified (by me, actually) to be agnostic to converters well before it hit 1.0 a few years ago. The point was that as libraries and formats change in popularity, no changes in Retrofit would be needed to support them. This is very much the case with the forthcoming version 2.0 as well.

As such, we won't be adding any more first-party converters unless a compelling reason can be made as to why we should. Since we already have 3 JSON converters, there is little argument to be made for more.

However, there's no reason that these cannot be turned into third-party libraries that exist on their own. I have [made a wiki page](https://github.com/square/retrofit/wiki/Converters) for listing third-party converters so that they are able to be discovered in one place.

Thanks for taking the time to implement this, despite that fact that we aren't able to accept it into the main repo.

@jfragosoperez @sockeqwe Version 1.0.0 of the LoganSquare converter has been released [and added to the wiki](https://github.com/square/retrofit/wiki/Converters)! Further maintenance will happen in its dedicated repository from now on.

Awesome! Thanks!

Awesome. Thanks @aurae for your contribution


Pull request review comments:
Remove this whole comment. This class isn't public.

Indent with two spaces

Delete comment

Delete comment

Delete comment

change "as its conversion backbone" to "for JSON" to match others.

Remove author tag

Add private constructor and static `create()` method which calls it

This needs to check if the `type` is not a `Class` and throw an exception with a meaningful message. The implicit `ClassCastException` that's thrown here is unsatisfactory.

Also, does this mean I can't use LoganSquare on a `List` of objects? That's really strange...

Delete whole comment

Add `final`

Needs a test verifying that using a type of `List<Foo>` throws an appropriate error message.

(that applies to all these files)

The second paragraph of other converters focuses on details you need to know for configuring. Since this library has no runtime configuration, you end up describing implementation details of the converter here.

Maybe change it to "The `JsonMapper` instances generated by the compiler are used for conversion."

You absolutely can use it on Lists (and String-keyed Maps, for that matter). I'm not sure what I was thinking here. I will update the PR with List and Map support asap.


Patch that resolved the issue:
diff --git a/retrofit-converters/logansquare/README.md b/retrofit-converters/logansquare/README.md
new file mode 100644
index 0000000000..be97bde11b
--- /dev/null
+++ b/retrofit-converters/logansquare/README.md
@@ -0,0 +1,8 @@
+LoganSquare Converter
+=================
+
+A `Converter` which uses [LoganSquare][1] for serialization to and from JSON.
+
+The `JsonMapper` instances generated by the compiler are used for conversion.
+
+ [1]: https://github.com/bluelinelabs/LoganSquare
diff --git a/retrofit-converters/logansquare/pom.xml b/retrofit-converters/logansquare/pom.xml
new file mode 100644
index 0000000000..04c9b6185d
--- /dev/null
+++ b/retrofit-converters/logansquare/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.squareup.retrofit</groupId>
+    <artifactId>retrofit-converters</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>converter-logansquare</artifactId>
+  <name>Converter: LoganSquare</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.retrofit</groupId>
+      <artifactId>retrofit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.bluelinelabs</groupId>
+      <artifactId>logansquare-compiler</artifactId>
+      <version>1.1.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareConverterFactory.java b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareConverterFactory.java
new file mode 100644
index 0000000000..84207cb208
--- /dev/null
+++ b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareConverterFactory.java
@@ -0,0 +1,67 @@
+package retrofit;
+
+import com.bluelinelabs.logansquare.JsonMapper;
+import com.bluelinelabs.logansquare.LoganSquare;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+
+/**
+ * A {@linkplain Converter.Factory converter} which uses LoganSquare for JSON.
+ *
+ * @see <a>https://github.com/bluelinelabs/LoganSquare</a>
+ */
+public final class LoganSquareConverterFactory implements Converter.Factory {
+	/**
+	 * Create an instance. Encoding to JSON and decoding from JSON will use UTF-8.
+	 */
+	public static LoganSquareConverterFactory create() {
+		return new LoganSquareConverterFactory();
+	}
+
+
+	private LoganSquareConverterFactory() {
+	}
+
+
+	private JsonMapper<?> mapperFor(Type type) {
+		return LoganSquare.mapperFor((Class<?>) type);
+	}
+
+
+	@Override public Converter<?> get(Type type) {
+		if (type instanceof Class) {
+			// Return a plain Object converter
+			return new LoganSquareObjectConverter<>(mapperFor(type));
+
+		} else if (type instanceof ParameterizedType) {
+			// Return a List or Map converter
+			ParameterizedType parameterizedType = (ParameterizedType) type;
+			Type[] typeArguments = parameterizedType.getActualTypeArguments();
+			Type firstType = typeArguments[0];
+
+			// Check for Map arguments
+			if (parameterizedType.getRawType() == Map.class) {
+				Type secondType = typeArguments[1];
+
+				// Perform validity checks on the type arguments, since LoganSquare works only on String keys
+				if (firstType != String.class) throw new RuntimeException("LoganSquareConverter can't convert Map keys of type '" + firstType + '\'');
+				if (!(secondType instanceof Class)) throw new RuntimeException("LoganSquareConverter can't convert Map values of type '" + secondType + '\'');
+
+				// Return a Map converter
+				return new LoganSquareMapConverter<>(mapperFor(secondType));
+
+			} else {
+				// Otherwise, access the ParametrizedType's only type argument and perform validity checks on it
+				if (!(firstType instanceof Class)) throw new RuntimeException("LoganSquareConverter can't convert List items of type '" + firstType + '\'');
+
+				// Return a List converter
+				return new LoganSquareListConverter<>(mapperFor(firstType));
+			}
+		}
+
+		throw new RuntimeException("LoganSquareConverter encountered non-convertable type '" + type + '\'');
+	}
+}
diff --git a/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareConverterUtils.java b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareConverterUtils.java
new file mode 100644
index 0000000000..f57667be73
--- /dev/null
+++ b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareConverterUtils.java
@@ -0,0 +1,8 @@
+package retrofit;
+
+import com.squareup.okhttp.MediaType;
+
+
+abstract class LoganSquareConverterUtils {
+	static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
+}
diff --git a/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareListConverter.java b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareListConverter.java
new file mode 100644
index 0000000000..b02e95f33f
--- /dev/null
+++ b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareListConverter.java
@@ -0,0 +1,38 @@
+package retrofit;
+
+import com.bluelinelabs.logansquare.JsonMapper;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.ResponseBody;
+
+import java.io.IOException;
+import java.util.List;
+
+import static retrofit.LoganSquareConverterUtils.MEDIA_TYPE;
+
+
+final class LoganSquareListConverter<T> implements Converter<List<T>> {
+
+
+	private final JsonMapper<T> mapper;
+
+
+	public LoganSquareListConverter(JsonMapper<T> mapper) {
+		this.mapper = mapper;
+	}
+
+
+	@Override public List<T> fromBody(ResponseBody body) throws IOException {
+		return mapper.parseList(body.byteStream());
+	}
+
+
+	@Override public RequestBody toBody(List<T> value) {
+		String serializedValue;
+		try {
+			serializedValue = mapper.serialize(value);
+		} catch (IOException ex) {
+			throw new AssertionError(ex);
+		}
+		return RequestBody.create(MEDIA_TYPE, serializedValue);
+	}
+}
diff --git a/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareMapConverter.java b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareMapConverter.java
new file mode 100644
index 0000000000..d38eddab4e
--- /dev/null
+++ b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareMapConverter.java
@@ -0,0 +1,38 @@
+package retrofit;
+
+import com.bluelinelabs.logansquare.JsonMapper;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.ResponseBody;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static retrofit.LoganSquareConverterUtils.MEDIA_TYPE;
+
+
+final class LoganSquareMapConverter<T> implements Converter<Map<String, T>> {
+
+
+	private final JsonMapper<T> mapper;
+
+
+	public LoganSquareMapConverter(JsonMapper<T> mapper) {
+		this.mapper = mapper;
+	}
+
+
+	@Override public Map<String, T> fromBody(ResponseBody body) throws IOException {
+		return mapper.parseMap(body.byteStream());
+	}
+
+
+	@Override public RequestBody toBody(Map<String, T> value) {
+		String serializedValue;
+		try {
+			serializedValue = mapper.serialize(value);
+		} catch (IOException ex) {
+			throw new AssertionError(ex);
+		}
+		return RequestBody.create(MEDIA_TYPE, serializedValue);
+	}
+}
diff --git a/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareObjectConverter.java b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareObjectConverter.java
new file mode 100644
index 0000000000..6d4f558355
--- /dev/null
+++ b/retrofit-converters/logansquare/src/main/java/retrofit/LoganSquareObjectConverter.java
@@ -0,0 +1,37 @@
+package retrofit;
+
+import com.bluelinelabs.logansquare.JsonMapper;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.ResponseBody;
+
+import java.io.IOException;
+
+import static retrofit.LoganSquareConverterUtils.MEDIA_TYPE;
+
+
+final class LoganSquareObjectConverter<T> implements Converter<T> {
+
+
+	private final JsonMapper<T> mapper;
+
+
+	public LoganSquareObjectConverter(JsonMapper<T> mapper) {
+		this.mapper = mapper;
+	}
+
+
+	@Override public T fromBody(ResponseBody body) throws IOException {
+		return mapper.parse(body.byteStream());
+	}
+
+
+	@Override public RequestBody toBody(T value) {
+		String serializedValue;
+		try {
+			serializedValue = mapper.serialize(value);
+		} catch (IOException ex) {
+			throw new AssertionError(ex);
+		}
+		return RequestBody.create(MEDIA_TYPE, serializedValue);
+	}
+}


Test patch:
diff --git a/retrofit-converters/logansquare/src/test/java/retrofit/LoganSquareConverterTest.java b/retrofit-converters/logansquare/src/test/java/retrofit/LoganSquareConverterTest.java
new file mode 100644
index 0000000000..7a419672d0
--- /dev/null
+++ b/retrofit-converters/logansquare/src/test/java/retrofit/LoganSquareConverterTest.java
@@ -0,0 +1,286 @@
+package retrofit;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import retrofit.http.Body;
+import retrofit.http.POST;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for the {@linkplain LoganSquareObjectConverter}.
+ *
+ * @author marcel
+ */
+public class LoganSquareConverterTest {
+
+	@JsonObject
+	static class Implementation {
+
+		@JsonField
+		String name;
+
+		@JsonField(typeConverter = CustomTypeConverter.class)
+		CustomType customType;
+
+		@JsonField(name = "list")
+		List<String> values;
+
+		String notSerialized;
+
+		public Implementation() {
+
+		}
+
+		public Implementation(String name, String notSerialized, CustomType customType, List<String> values) {
+			this();
+			this.name = name;
+			this.notSerialized = notSerialized;
+			this.customType = customType;
+			this.values = values;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public String getNotSerialized() {
+			return notSerialized;
+		}
+
+		public CustomType getCustomType() {
+			return customType;
+		}
+
+		public List<String> getValues() {
+			return values;
+		}
+	}
+
+	enum CustomType {
+		VAL_1(1),
+		VAL_2(2);
+
+		private int val;
+
+		CustomType(int val) {
+			this.val = val;
+		}
+
+		static CustomType fromInt(int val) {
+			for (CustomType customType : values()) {
+				if (customType.val == val) return customType;
+			}
+			throw new AssertionError("custom type with value " + val + " not found.");
+		}
+	}
+
+	static class CustomTypeConverter implements TypeConverter<CustomType> {
+
+		@Override public CustomType parse(JsonParser jsonParser) throws IOException {
+			return CustomType.fromInt(jsonParser.getIntValue());
+		}
+
+		@Override public void serialize(CustomType object, String fieldName, boolean writeFieldNameForObject, JsonGenerator jsonGenerator) throws IOException {
+			jsonGenerator.writeFieldName(fieldName);
+			jsonGenerator.writeNumber(object.val);
+		}
+	}
+
+	interface Service {
+		@POST("/") Call<Implementation> callImplementation(@Body Implementation body);
+		@POST("/") Call<List<Implementation>> callList(@Body List<Implementation> body);
+		@POST("/") Call<Implementation> callListWrongType(@Body List<List<Implementation>> body);
+		@POST("/") Call<Map<String, Implementation>> callMap(@Body Map<String, Implementation> body);
+		@POST("/") Call<Map<Integer, Implementation>> callMapWrongKey(@Body Map<Integer, Implementation> body);
+		@POST("/") Call<Map<String, List<Implementation>>> callMapWrongValue(@Body Map<String, List<Implementation>> body);
+		@POST("/") Call<Implementation[]> callArray(@Body Implementation[] body);
+	}
+
+	@Rule public final MockWebServer mockWebServer = new MockWebServer();
+
+	private Service service;
+
+	@Before public void setUp() {
+		service = new Retrofit.Builder()
+				.baseUrl(mockWebServer.url("/"))
+				.converterFactory(LoganSquareConverterFactory.create())
+				.build()
+				.create(Service.class);
+	}
+
+	@Test public void testObject() throws IOException, InterruptedException {
+		// Enqueue a mock response
+		mockWebServer.enqueue(new MockResponse().setBody("{\"customType\":2,\"name\":\"LOGAN SQUARE IS COOL\",\"list\":[\"value1\",\"value2\"]}"));
+
+		// Setup the mock object
+		List<String> values = new ArrayList<>();
+		values.add("value1");
+		values.add("value2");
+		Implementation requestBody = new Implementation("LOGAN SQUARE IS COOL", "Not serialized", CustomType.VAL_2, values);
+
+		// Call the API and execute it
+		Call<Implementation> call = service.callImplementation(requestBody);
+		Response<Implementation> response = call.execute();
+		Implementation responseBody = response.body();
+
+		// Assert that conversions worked
+		// 1) Standard field declaration
+		assertThat(responseBody.getName()).isEqualTo("LOGAN SQUARE IS COOL");
+		// 2) Named field declaration & List serialization
+		assertThat(responseBody.getValues()).containsExactly("value1", "value2");
+		// 3) Custom Type adapter serialization
+		assertThat(responseBody.getCustomType()).isEqualTo(CustomType.VAL_2);
+		// 4) Excluded field
+		assertThat(responseBody.getNotSerialized()).isNull();
+
+		// Check request body and the received header
+		RecordedRequest request = mockWebServer.takeRequest();
+		assertThat(request.getBody().readUtf8()).isEqualTo("{\"customType\":2,\"name\":\"LOGAN SQUARE IS COOL\",\"list\":[\"value1\",\"value2\"]}");
+		assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
+	}
+
+	@Test public void testList() throws IOException, InterruptedException {
+		// Enqueue a mock response
+		mockWebServer.enqueue(new MockResponse().setBody("[{\"customType\":2,\"name\":\"LOGAN SQUARE IS COOL\",\"list\":[\"value1\",\"value2\"]},{\"customType\":1,\"name\":\"LOGAN SQUARE IS COOL2\",\"list\":[\"value1\",\"value2\"]}]"));
+
+		// Setup the mock object
+		List<String> values = new ArrayList<>();
+		values.add("value1");
+		values.add("value2");
+		List<Implementation> requestBody = new ArrayList<>();
+		requestBody.add(new Implementation("LOGAN SQUARE IS COOL", "Not serialized", CustomType.VAL_2, values));
+		requestBody.add(new Implementation("LOGAN SQUARE IS COOL2", "Not serialized", CustomType.VAL_1, values));
+
+		// Call the API and execute it
+		Call<List<Implementation>> call = service.callList(requestBody);
+		Response<List<Implementation>> response = call.execute();
+		List<Implementation> responseBody = response.body();
+
+		// Assert that conversions worked
+		// Number of objects
+		assertThat(responseBody).hasSize(2);
+		// Member values of first object
+		Implementation o1 = responseBody.get(0);
+		assertThat(o1.getName()).isEqualTo("LOGAN SQUARE IS COOL");
+		assertThat(o1.getValues()).containsExactly("value1", "value2");
+		assertThat(o1.getCustomType()).isEqualTo(CustomType.VAL_2);
+		assertThat(o1.getNotSerialized()).isNull();
+		// Member values of second object
+		Implementation o2 = responseBody.get(1);
+		assertThat(o2.getName()).isEqualTo("LOGAN SQUARE IS COOL2");
+		assertThat(o2.getValues()).containsExactly("value1", "value2");
+		assertThat(o2.getCustomType()).isEqualTo(CustomType.VAL_1);
+		assertThat(o2.getNotSerialized()).isNull();
+
+		// Check request body and the received header
+		RecordedRequest request = mockWebServer.takeRequest();
+		assertThat(request.getBody().readUtf8()).isEqualTo("[{\"customType\":2,\"name\":\"LOGAN SQUARE IS COOL\",\"list\":[\"value1\",\"value2\"]},{\"customType\":1,\"name\":\"LOGAN SQUARE IS COOL2\",\"list\":[\"value1\",\"value2\"]}]");
+		assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
+	}
+
+	@Test public void testListWrongType() throws IOException {
+		// Setup the mock object with an incompatible type argument
+		List<List<Implementation>> body = new ArrayList<>();
+
+		// Setup the API call and fire it
+		try {
+			service.callListWrongType(body);
+			Assertions.failBecauseExceptionWasNotThrown(RuntimeException.class);
+		} catch (RuntimeException ex) {
+		}
+	}
+
+	@Test public void testMap() throws IOException, InterruptedException {
+		// Enqueue a mock response
+		mockWebServer.enqueue(new MockResponse().setBody("{\"item1\":{\"customType\":2,\"name\":\"LOGAN SQUARE IS COOL\",\"list\":[\"value1\",\"value2\"]},\"item2\":{\"customType\":1,\"name\":\"LOGAN SQUARE IS COOL2\",\"list\":[\"value1\",\"value2\"]}}"));
+
+		// Setup the mock object
+		List<String> values = new ArrayList<>();
+		values.add("value1");
+		values.add("value2");
+		Map<String, Implementation> requestBody = new HashMap<>();
+		requestBody.put("item1", new Implementation("LOGAN SQUARE IS COOL", "Not serialized", CustomType.VAL_2, values));
+		requestBody.put("item2", new Implementation("LOGAN SQUARE IS COOL2", "Not serialized", CustomType.VAL_1, values));
+
+		// Call the API and execute it
+		Call<Map<String, Implementation>> call = service.callMap(requestBody);
+		Response<Map<String, Implementation>> response = call.execute();
+		Map<String, Implementation> responseBody = response.body();
+
+		// Assert that conversions worked
+		// Number of objects
+		assertThat(responseBody).hasSize(2);
+		// Member values of first object
+		Implementation o1 = responseBody.get("item1");
+		assertThat(o1.getName()).isEqualTo("LOGAN SQUARE IS COOL");
+		assertThat(o1.getValues()).containsExactly("value1", "value2");
+		assertThat(o1.getCustomType()).isEqualTo(CustomType.VAL_2);
+		assertThat(o1.getNotSerialized()).isNull();
+		// Member values of second object
+		Implementation o2 = responseBody.get("item2");
+		assertThat(o2.getName()).isEqualTo("LOGAN SQUARE IS COOL2");
+		assertThat(o2.getValues()).containsExactly("value1", "value2");
+		assertThat(o2.getCustomType()).isEqualTo(CustomType.VAL_1);
+		assertThat(o2.getNotSerialized()).isNull();
+
+		// Check request body and the received header
+		RecordedRequest request = mockWebServer.takeRequest();
+		assertThat(request.getBody().readUtf8()).isEqualTo("{\"item2\":{\"customType\":1,\"name\":\"LOGAN SQUARE IS COOL2\",\"list\":[\"value1\",\"value2\"]},\"item1\":{\"customType\":2,\"name\":\"LOGAN SQUARE IS COOL\",\"list\":[\"value1\",\"value2\"]}}");
+		assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
+	}
+
+	@Test public void testMapWrongKeyType() throws IOException {
+		// Setup the mock object with an incompatible type argument
+		Map<Integer, Implementation> body = new HashMap<>();
+
+		// Setup the API call and fire it
+		try {
+			service.callMapWrongKey(body);
+			Assertions.failBecauseExceptionWasNotThrown(RuntimeException.class);
+		} catch (RuntimeException ex) {
+		}
+	}
+
+	@Test public void testMapWrongValueType() throws IOException {
+		// Setup the mock object with an incompatible type argument
+		Map<String, List<Implementation>> body = new HashMap<>();
+
+		// Setup the API call and fire it
+		try {
+			service.callMapWrongValue(body);
+			Assertions.failBecauseExceptionWasNotThrown(RuntimeException.class);
+		} catch (RuntimeException ex) {
+		}
+	}
+
+	@Test public void testFailWhenArray() throws IOException {
+		// Setup the mock object with an incompatible type argument
+		Implementation[] body = new Implementation[0];
+
+		// Setup the API call and fire it
+		try {
+			service.callArray(body);
+			Assertions.failBecauseExceptionWasNotThrown(RuntimeException.class);
+		} catch (RuntimeException ex) {
+		}
+	}
+}