Skip to content

Commit 58aa3bc

Browse files
author
adriancole
committed
Gson type adapters can be registered as Dagger set bindings.
1 parent b607fe3 commit 58aa3bc

3 files changed

Lines changed: 93 additions & 7 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use single non-generic Decoder/Encoder instead of sets of type-specific Decoders/Encoders.
66
* Decoders/Encoders are now more flexible, having access to the Response/RequestTemplate respectively.
77
* Added Feign.Builder to simplify client customizations without using Dagger.
8+
* Gson type adapters can be registered as Dagger set bindings.
89

910
### Version 4.4.1
1011
* Fix NullPointerException on calling equals and hashCode.

gson/src/main/java/feign/gson/GsonModule.java

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,47 @@
3838
import java.lang.reflect.Type;
3939
import java.util.Collections;
4040
import java.util.Map;
41+
import java.util.Set;
4142

4243
import static feign.Util.ensureClosed;
43-
44+
import static feign.Util.resolveLastTypeParameter;
45+
46+
/**
47+
* <h3>Custom type adapters</h3>
48+
* <br>
49+
* In order to specify custom json parsing,
50+
* {@code Gson} supports {@link TypeAdapter type adapters}. This module adds one
51+
* to read numbers in a {@code Map<String, Object>} as Integers. You can
52+
* customize further by adding additional set bindings to the raw type
53+
* {@code TypeAdapter}.
54+
*
55+
* <br>
56+
* Here's an example of adding a custom json type adapter.
57+
*
58+
* <pre>
59+
* &#064;Provides(type = Provides.Type.SET)
60+
* TypeAdapter upperZone() {
61+
* return new TypeAdapter&lt;Zone&gt;() {
62+
*
63+
* &#064;Override
64+
* public void write(JsonWriter out, Zone value) throws IOException {
65+
* throw new IllegalArgumentException();
66+
* }
67+
*
68+
* &#064;Override
69+
* public Zone read(JsonReader in) throws IOException {
70+
* in.beginObject();
71+
* Zone zone = new Zone();
72+
* while (in.hasNext()) {
73+
* zone.put(in.nextName(), in.nextString().toUpperCase());
74+
* }
75+
* in.endObject();
76+
* return zone;
77+
* }
78+
* };
79+
* }
80+
* </pre>
81+
*/
4482
@dagger.Module(library = true)
4583
public final class GsonModule {
4684

@@ -87,8 +125,17 @@ private Object fromJson(JsonReader jsonReader, Type type) throws IOException {
87125
}
88126
}
89127

128+
@Provides @Singleton Gson gson(Set<TypeAdapter> adapters) {
129+
GsonBuilder builder = new GsonBuilder().setPrettyPrinting();
130+
for (TypeAdapter<?> adapter : adapters) {
131+
Type type = resolveLastTypeParameter(adapter.getClass(), TypeAdapter.class);
132+
builder.registerTypeAdapter(type, adapter);
133+
}
134+
return builder.create();
135+
}
136+
90137
// deals with scenario where gson Object type treats all numbers as doubles.
91-
@Provides TypeAdapter<Map<String, Object>> doubleToInt() {
138+
@Provides(type = Provides.Type.SET) TypeAdapter doubleToInt() {
92139
return new TypeAdapter<Map<String, Object>>() {
93140
TypeAdapter<Map<String, Object>> delegate = new MapTypeAdapterFactory(new ConstructorConstructor(
94141
Collections.<Type, InstanceCreator<?>>emptyMap()), false).create(new Gson(), token);
@@ -111,10 +158,6 @@ public Map<String, Object> read(JsonReader in) throws IOException {
111158
}.nullSafe();
112159
}
113160

114-
@Provides @Singleton Gson gson(TypeAdapter<Map<String, Object>> doubleToInt) {
115-
return new GsonBuilder().registerTypeAdapter(token.getType(), doubleToInt).setPrettyPrinting().create();
116-
}
117-
118-
protected final static TypeToken<Map<String, Object>> token = new TypeToken<Map<String, Object>>() {
161+
private final static TypeToken<Map<String, Object>> token = new TypeToken<Map<String, Object>>() {
119162
};
120163
}

gson/src/test/java/feign/gson/GsonModuleTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@
1515
*/
1616
package feign.gson;
1717

18+
import com.google.gson.TypeAdapter;
1819
import com.google.gson.reflect.TypeToken;
20+
import com.google.gson.stream.JsonReader;
21+
import com.google.gson.stream.JsonWriter;
1922
import dagger.Module;
2023
import dagger.ObjectGraph;
24+
import dagger.Provides;
2125
import feign.RequestTemplate;
2226
import feign.Response;
2327
import feign.codec.Decoder;
2428
import feign.codec.Encoder;
2529
import org.testng.annotations.Test;
2630

2731
import javax.inject.Inject;
32+
import java.io.IOException;
2833
import java.util.Arrays;
2934
import java.util.Collection;
3035
import java.util.Collections;
@@ -138,4 +143,41 @@ static class DecoderBindings {
138143
+ " \"id\": \"ABCD\"\n"//
139144
+ " }\n"//
140145
+ "]\n";
146+
147+
@Module(includes = GsonModule.class, library = true, injects = CustomTypeAdapter.class)
148+
static class CustomTypeAdapter {
149+
@Provides(type = Provides.Type.SET) TypeAdapter upperZone() {
150+
return new TypeAdapter<Zone>() {
151+
152+
@Override public void write(JsonWriter out, Zone value) throws IOException {
153+
throw new IllegalArgumentException();
154+
}
155+
156+
@Override public Zone read(JsonReader in) throws IOException {
157+
in.beginObject();
158+
Zone zone = new Zone();
159+
while (in.hasNext()) {
160+
zone.put(in.nextName(), in.nextString().toUpperCase());
161+
}
162+
in.endObject();
163+
return zone;
164+
}
165+
};
166+
}
167+
168+
@Inject Decoder decoder;
169+
}
170+
171+
@Test public void customDecoder() throws Exception {
172+
CustomTypeAdapter bindings = new CustomTypeAdapter();
173+
ObjectGraph.create(bindings).inject(bindings);
174+
175+
List<Zone> zones = new LinkedList<Zone>();
176+
zones.add(new Zone("DENOMINATOR.IO."));
177+
zones.add(new Zone("DENOMINATOR.IO.", "ABCD"));
178+
179+
Response response = Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), zonesJson);
180+
assertEquals(bindings.decoder.decode(response, new TypeToken<List<Zone>>() {
181+
}.getType()), zones);
182+
}
141183
}

0 commit comments

Comments
 (0)