Browse Source

Simplify usage of Gson from Feign.Builder

The logic in GsonCodec was split into GsonEncoder and GsonDecoder, each of which can
now be used separately.  GsonCodec was deprecated, and can be removed in the next major
version.  To facilitate use outside of Dagger, the double-to-int map type adapter was broken into
its own class, and is included by default when using the default constructors of either the
encoder or decoder.  The examples have been updated to use the new encoder/decoder instead
of the codec.
pull/80/head
David M. Carr 12 years ago committed by adriancole
parent
commit
fe5e8d150b
  1. 4
      CHANGES.md
  2. 14
      README.md
  3. 11
      gson/README.md
  4. 54
      gson/src/main/java/feign/gson/DoubleToIntMapTypeAdapter.java
  5. 31
      gson/src/main/java/feign/gson/GsonCodec.java
  6. 56
      gson/src/main/java/feign/gson/GsonDecoder.java
  7. 36
      gson/src/main/java/feign/gson/GsonEncoder.java
  8. 43
      gson/src/main/java/feign/gson/GsonModule.java
  9. 4
      gson/src/test/java/feign/gson/GsonModuleTest.java
  10. 4
      gson/src/test/java/feign/gson/examples/GitHubExample.java

4
CHANGES.md

@ -1,3 +1,7 @@
### Version 5.3.0
* Split `GsonCodec` into `GsonEncoder` and `GsonDecoder`, which are easy to use with `Feign.Builder`
* Deprecate `GsonCodec`
### Version 5.2.0 ### Version 5.2.0
* Support usage of `GsonCodec` via `Feign.Builder` * Support usage of `GsonCodec` via `Feign.Builder`

14
README.md

@ -26,7 +26,7 @@ static class Contributor {
public static void main(String... args) { public static void main(String... args) {
GitHub github = Feign.builder() GitHub github = Feign.builder()
.decoder(new GsonCodec()) .decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com"); .target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library. // Fetch and print a list of the contributors to this library.
@ -83,13 +83,13 @@ Feign intends to work well within Netflix and other Open Source communities. Mo
### Gson ### Gson
[GsonModule](https://github.com/Netflix/feign/tree/master/gson) adds default encoders and decoders so you get get started with a JSON api. [GsonModule](https://github.com/Netflix/feign/tree/master/gson) adds default encoders and decoders so you get get started with a JSON api.
Add `GsonCodec` to your `Feign.Builder` like so: Add `GsonEncoder` and/or `GsonDecoder` to your `Feign.Builder` like so:
```java ```java
GsonCodec codec = new GsonCodec(); GsonCodec codec = new GsonCodec();
GitHub github = Feign.builder() GitHub github = Feign.builder()
.encoder(codec) .encoder(new GsonEncoder())
.decoder(codec) .decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com"); .target(GitHub.class, "https://api.github.com");
``` ```
@ -126,13 +126,13 @@ MyService api = Feign.create(MyService.class, "https://myAppProd", new RibbonMod
### Decoders ### Decoders
`Feign.builder()` allows you to specify additional configuration such as how to decode a response. `Feign.builder()` allows you to specify additional configuration such as how to decode a response.
If any methods in your interface return types besides `Response` or `void`, you'll need to configure a `Decoder`. If any methods in your interface return types besides `Response`, `String` or `void`, you'll need to configure a `Decoder`.
Here's how to configure json decoding (using the `feign-gson` extension): Here's how to configure json decoding (using the `feign-gson` extension):
```java ```java
GitHub github = Feign.builder() GitHub github = Feign.builder()
.decoder(new GsonCodec()) .decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com"); .target(GitHub.class, "https://api.github.com");
``` ```
@ -162,7 +162,7 @@ Where possible, Feign configuration uses normal Dagger conventions. For example
You can log the http messages going to and from the target by setting up a `Logger`. Here's the easiest way to do that: You can log the http messages going to and from the target by setting up a `Logger`. Here's the easiest way to do that:
```java ```java
GitHub github = Feign.builder() GitHub github = Feign.builder()
.decoder(new GsonCodec()) .decoder(new GsonDecoder())
.logger(new Logger.JavaLogger().appendToFile("logs/http.log")) .logger(new Logger.JavaLogger().appendToFile("logs/http.log"))
.logLevel(Logger.Level.FULL) .logLevel(Logger.Level.FULL)
.target(GitHub.class, "https://api.github.com"); .target(GitHub.class, "https://api.github.com");

11
gson/README.md

@ -1,19 +1,18 @@
Gson Codec Gson Codec
=================== ===================
This module adds support for encoding and decoding json via the Gson library. This module adds support for encoding and decoding JSON via the Gson library.
Add `GsonCodec` to your `Feign.Builder` like so: Add `GsonEncoder` and/or `GsonDecoder` to your `Feign.Builder` like so:
```java ```java
GsonCodec codec = new GsonCodec();
GitHub github = Feign.builder() GitHub github = Feign.builder()
.encoder(codec) .encoder(new GsonEncoder())
.decoder(codec) .decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com"); .target(GitHub.class, "https://api.github.com");
``` ```
Or.. to your object graph like so: Or add them to your Dagger object graph like so:
```java ```java
GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GsonModule()); GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GsonModule());

54
gson/src/main/java/feign/gson/DoubleToIntMapTypeAdapter.java

@ -0,0 +1,54 @@
/*
* Copyright 2013 Netflix, 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 feign.gson;
import com.google.gson.Gson;
import com.google.gson.InstanceCreator;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.bind.MapTypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
/**
* Deals with scenario where Gson Object type treats all numbers as doubles.
*/
public class DoubleToIntMapTypeAdapter extends TypeAdapter<Map<String, Object>> {
final static TypeToken<Map<String, Object>> token = new TypeToken<Map<String, Object>>() {};
private final TypeAdapter<Map<String, Object>> delegate = new MapTypeAdapterFactory(new ConstructorConstructor(
Collections.<Type, InstanceCreator<?>>emptyMap()), false).create(new Gson(), token);
@Override public void write(JsonWriter out, Map<String, Object> value) throws IOException {
delegate.write(out, value);
}
@Override public Map<String, Object> read(JsonReader in) throws IOException {
Map<String, Object> map = delegate.read(in);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Double) {
entry.setValue(Double.class.cast(entry.getValue()).intValue());
}
}
return map;
}
}

31
gson/src/main/java/feign/gson/GsonCodec.java

@ -1,7 +1,6 @@
package feign.gson; package feign.gson;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.Response; import feign.Response;
import feign.codec.Decoder; import feign.codec.Decoder;
@ -9,40 +8,30 @@ import feign.codec.Encoder;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.IOException; import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import static feign.Util.ensureClosed; /**
* @deprecated use {@link GsonEncoder} and {@link GsonDecoder} instead
*/
@Deprecated
public class GsonCodec implements Encoder, Decoder { public class GsonCodec implements Encoder, Decoder {
private final Gson gson; private final GsonEncoder encoder;
private final GsonDecoder decoder;
public GsonCodec() { public GsonCodec() {
this(new Gson()); this(new Gson());
} }
@Inject public GsonCodec(Gson gson) { @Inject public GsonCodec(Gson gson) {
this.gson = gson; this.encoder = new GsonEncoder(gson);
this.decoder = new GsonDecoder(gson);
} }
@Override public void encode(Object object, RequestTemplate template) { @Override public void encode(Object object, RequestTemplate template) {
template.body(gson.toJson(object)); encoder.encode(object, template);
} }
@Override public Object decode(Response response, Type type) throws IOException { @Override public Object decode(Response response, Type type) throws IOException {
if (response.body() == null) { return decoder.decode(response, type);
return null;
}
Reader reader = response.body().asReader();
try {
return gson.fromJson(reader, type);
} catch (JsonIOException e) {
if (e.getCause() != null && e.getCause() instanceof IOException) {
throw IOException.class.cast(e.getCause());
}
throw e;
} finally {
ensureClosed(reader);
}
} }
} }

56
gson/src/main/java/feign/gson/GsonDecoder.java

@ -0,0 +1,56 @@
/*
* Copyright 2013 Netflix, 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 feign.gson;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import feign.Response;
import feign.codec.Decoder;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import static feign.Util.ensureClosed;
public class GsonDecoder implements Decoder {
private final Gson gson;
public GsonDecoder() {
this(new Gson());
}
public GsonDecoder(Gson gson) {
this.gson = gson;
}
@Override public Object decode(Response response, Type type) throws IOException {
if (response.body() == null) {
return null;
}
Reader reader = response.body().asReader();
try {
return gson.fromJson(reader, type);
} catch (JsonIOException e) {
if (e.getCause() != null && e.getCause() instanceof IOException) {
throw IOException.class.cast(e.getCause());
}
throw e;
} finally {
ensureClosed(reader);
}
}
}

36
gson/src/main/java/feign/gson/GsonEncoder.java

@ -0,0 +1,36 @@
/*
* Copyright 2013 Netflix, 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 feign.gson;
import com.google.gson.Gson;
import feign.RequestTemplate;
import feign.codec.Encoder;
public class GsonEncoder implements Encoder {
private final Gson gson;
public GsonEncoder() {
this(new Gson());
}
public GsonEncoder(Gson gson) {
this.gson = gson;
}
@Override public void encode(Object object, RequestTemplate template) {
template.body(gson.toJson(object));
}
}

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

@ -17,23 +17,15 @@ package feign.gson;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.bind.MapTypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import dagger.Provides; import dagger.Provides;
import feign.Feign; import feign.Feign;
import feign.codec.Decoder; import feign.codec.Decoder;
import feign.codec.Encoder; import feign.codec.Encoder;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Set; import java.util.Set;
import static feign.Util.resolveLastTypeParameter; import static feign.Util.resolveLastTypeParameter;
@ -77,12 +69,12 @@ import static feign.Util.resolveLastTypeParameter;
@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) @dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class)
public final class GsonModule { public final class GsonModule {
@Provides Encoder encoder(GsonCodec codec) { @Provides Encoder encoder(Gson gson) {
return codec; return new GsonEncoder(gson);
} }
@Provides Decoder decoder(GsonCodec codec) { @Provides Decoder decoder(Gson gson) {
return codec; return new GsonDecoder(gson);
} }
@Provides @Singleton Gson gson(Set<TypeAdapter> adapters) { @Provides @Singleton Gson gson(Set<TypeAdapter> adapters) {
@ -94,30 +86,7 @@ public final class GsonModule {
return builder.create(); return builder.create();
} }
// deals with scenario where gson Object type treats all numbers as doubles. @Provides(type = Provides.Type.SET_VALUES) Set<TypeAdapter> noDefaultTypeAdapters() {
@Provides(type = Provides.Type.SET) TypeAdapter doubleToInt() { return Collections.emptySet();
return new TypeAdapter<Map<String, Object>>() {
TypeAdapter<Map<String, Object>> delegate = new MapTypeAdapterFactory(new ConstructorConstructor(
Collections.<Type, InstanceCreator<?>>emptyMap()), false).create(new Gson(), token);
@Override
public void write(JsonWriter out, Map<String, Object> value) throws IOException {
delegate.write(out, value);
}
@Override
public Map<String, Object> read(JsonReader in) throws IOException {
Map<String, Object> map = delegate.read(in);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Double) {
entry.setValue(Double.class.cast(entry.getValue()).intValue());
}
}
return map;
}
}.nullSafe();
} }
private final static TypeToken<Map<String, Object>> token = new TypeToken<Map<String, Object>>() {
};
} }

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

@ -52,8 +52,8 @@ public class GsonModuleTest {
EncoderAndDecoderBindings bindings = new EncoderAndDecoderBindings(); EncoderAndDecoderBindings bindings = new EncoderAndDecoderBindings();
ObjectGraph.create(bindings).inject(bindings); ObjectGraph.create(bindings).inject(bindings);
assertEquals(bindings.encoder.getClass(), GsonCodec.class); assertEquals(bindings.encoder.getClass(), GsonEncoder.class);
assertEquals(bindings.decoder.getClass(), GsonCodec.class); assertEquals(bindings.decoder.getClass(), GsonDecoder.class);
} }
@Module(includes = GsonModule.class, injects = EncoderBindings.class) @Module(includes = GsonModule.class, injects = EncoderBindings.class)

4
gson/src/test/java/feign/gson/examples/GitHubExample.java

@ -17,7 +17,7 @@ package feign.gson.examples;
import feign.Feign; import feign.Feign;
import feign.RequestLine; import feign.RequestLine;
import feign.gson.GsonCodec; import feign.gson.GsonDecoder;
import javax.inject.Named; import javax.inject.Named;
import java.util.List; import java.util.List;
@ -38,7 +38,7 @@ public class GitHubExample {
} }
public static void main(String... args) throws InterruptedException { public static void main(String... args) throws InterruptedException {
GitHub github = Feign.builder().decoder(new GsonCodec()).target(GitHub.class, "https://api.github.com"); GitHub github = Feign.builder().decoder(new GsonDecoder()).target(GitHub.class, "https://api.github.com");
System.out.println("Let's fetch and print a list of the contributors to this library."); System.out.println("Let's fetch and print a list of the contributors to this library.");
List<Contributor> contributors = github.contributors("netflix", "feign"); List<Contributor> contributors = github.contributors("netflix", "feign");

Loading…
Cancel
Save