-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Jackson Codec #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jackson Codec #82
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| Jackson Codec | ||
| =================== | ||
|
|
||
| This module adds support for encoding and decoding JSON via Jackson. | ||
|
|
||
| Add `JacksonEncoder` and/or `JacksonDecoder` to your `Feign.Builder` like so: | ||
|
|
||
| ```java | ||
| GitHub github = Feign.builder() | ||
| .encoder(new JacksonEncoder()) | ||
| .decoder(new JacksonDecoder()) | ||
| .target(GitHub.class, "https://api.github.com"); | ||
| ``` | ||
|
|
||
| If you want to customize the `ObjectMapper` that is used, provide it to the `JacksonEncoder` and `JacksonDecoder`: | ||
|
|
||
| ```java | ||
| ObjectMapper mapper = new ObjectMapper() | ||
| .setSerializationInclusion(JsonInclude.Include.NON_NULL) | ||
| .configure(SerializationFeature.INDENT_OUTPUT, true) | ||
| .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | ||
|
|
||
| GitHub github = Feign.builder() | ||
| .encoder(new JacksonEncoder(mapper)) | ||
| .decoder(new JacksonDecoder(mapper)) | ||
| .target(GitHub.class, "https://api.github.com"); | ||
| ``` | ||
|
|
||
| Alternatively, you can add the encoder and decoder to your Dagger object graph using the provided `JacksonModule` like so: | ||
|
|
||
| ```java | ||
| GitHub github = Feign.create(GitHub.class, "https://api.github.com", new JacksonModule()); | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| * 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.jackson; | ||
|
|
||
| import com.fasterxml.jackson.databind.DeserializationFeature; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.fasterxml.jackson.databind.RuntimeJsonMappingException; | ||
| import feign.Response; | ||
| import feign.codec.Decoder; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.Reader; | ||
| import java.lang.reflect.Type; | ||
|
|
||
| public class JacksonDecoder implements Decoder { | ||
| private final ObjectMapper mapper; | ||
|
|
||
| public JacksonDecoder() { | ||
| this(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)); | ||
| } | ||
|
|
||
| public JacksonDecoder(ObjectMapper mapper) { | ||
| this.mapper = mapper; | ||
| } | ||
|
|
||
| @Override public Object decode(Response response, Type type) throws IOException { | ||
| if (response.body() == null) { | ||
| return null; | ||
| } | ||
| Reader reader = response.body().asReader(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick question: is there a way to get
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I know, currently there is no way to get an
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| try { | ||
| return mapper.readValue(reader, mapper.constructType(type)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So there are no jackson runtime exceptions thrown here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like a com.fasterxml.jackson.databind.RuntimeJsonMappingException is a possibility. Shall I catch that and inspect whether the cause is an IOException in the same fashion as the GsonDecoder?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If jackson wraps ioexception by design, then unwrap. Otherwise just coerce
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done; it does wrap IOException by design, so I've added code to unwrap it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Jackson's own exceptions all extend
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thx |
||
| } catch (RuntimeJsonMappingException e) { | ||
| if (e.getCause() != null && e.getCause() instanceof IOException) { | ||
| throw IOException.class.cast(e.getCause()); | ||
| } | ||
| throw e; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably clarify decoder javadoc that you dont need defensive finally close blocks. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * 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.jackson; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.fasterxml.jackson.databind.SerializationFeature; | ||
| import feign.RequestTemplate; | ||
| import feign.codec.EncodeException; | ||
| import feign.codec.Encoder; | ||
|
|
||
| public class JacksonEncoder implements Encoder { | ||
| private final ObjectMapper mapper; | ||
|
|
||
| public JacksonEncoder() { | ||
| this(new ObjectMapper() | ||
| .setSerializationInclusion(JsonInclude.Include.NON_NULL) | ||
| .configure(SerializationFeature.INDENT_OUTPUT, true)); | ||
| } | ||
|
|
||
| public JacksonEncoder(ObjectMapper mapper) { | ||
| this.mapper = mapper; | ||
| } | ||
|
|
||
| @Override public void encode(Object object, RequestTemplate template) throws EncodeException { | ||
| try { | ||
| template.body(mapper.writeValueAsString(object)); | ||
| } catch (JsonProcessingException e) { | ||
| throw new EncodeException(e.getMessage(), e); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| /* | ||
| * 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.jackson; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
| import com.fasterxml.jackson.databind.DeserializationFeature; | ||
| import com.fasterxml.jackson.databind.JsonDeserializer; | ||
| import com.fasterxml.jackson.databind.JsonSerializer; | ||
| import com.fasterxml.jackson.databind.Module; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.fasterxml.jackson.databind.SerializationFeature; | ||
| import dagger.Provides; | ||
| import feign.Feign; | ||
| import feign.codec.Decoder; | ||
| import feign.codec.Encoder; | ||
|
|
||
| import javax.inject.Singleton; | ||
| import java.util.Collections; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * <h3>Custom serializers/deserializers</h3> | ||
| * <br> | ||
| * In order to specify custom json parsing, Jackson's {@code ObjectMapper} supports {@link JsonSerializer serializers} | ||
| * and {@link JsonDeserializer deserializers}, which can be bundled together in {@link Module modules}. | ||
| * <p/> | ||
| * <br> | ||
| * Here's an example of adding a custom module. | ||
| * <p/> | ||
| * <pre> | ||
| * public class ObjectIdSerializer extends StdSerializer<ObjectId> { | ||
| * public ObjectIdSerializer() { | ||
| * super(ObjectId.class); | ||
| * } | ||
| * | ||
| * @Override | ||
| * public void serialize(ObjectId value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException { | ||
| * jsonGenerator.writeString(value.toString()); | ||
| * } | ||
| * } | ||
| * | ||
| * public class ObjectIdDeserializer extends StdDeserializer<ObjectId> { | ||
| * public ObjectIdDeserializer() { | ||
| * super(ObjectId.class); | ||
| * } | ||
| * | ||
| * @Override | ||
| * public ObjectId deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException { | ||
| * return ObjectId.massageToObjectId(jsonParser.getValueAsString()); | ||
| * } | ||
| * } | ||
| * | ||
| * public class ObjectIdModule extends SimpleModule { | ||
| * public ObjectIdModule() { | ||
| * // first deserializers | ||
| * addDeserializer(ObjectId.class, new ObjectIdDeserializer()); | ||
| * | ||
| * // then serializers: | ||
| * addSerializer(ObjectId.class, new ObjectIdSerializer()); | ||
| * } | ||
| * } | ||
| * | ||
| * @Provides(type = Provides.Type.SET) | ||
| * Module objectIdModule() { | ||
| * return new ObjectIdModule(); | ||
| * } | ||
| * </pre> | ||
| */ | ||
| @dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) | ||
| public final class JacksonModule { | ||
| @Provides Encoder encoder(ObjectMapper mapper) { | ||
| return new JacksonEncoder(mapper); | ||
| } | ||
|
|
||
| @Provides Decoder decoder(ObjectMapper mapper) { | ||
| return new JacksonDecoder(mapper); | ||
| } | ||
|
|
||
| @Provides @Singleton ObjectMapper mapper(Set<Module> modules) { | ||
| return new ObjectMapper() | ||
| .setSerializationInclusion(JsonInclude.Include.NON_NULL) | ||
| .configure(SerializationFeature.INDENT_OUTPUT, true) | ||
| .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) | ||
| .registerModules(modules); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these default ObjectMapper tweaks also appropriate for the default ObjectMapper used by the encoder and decoder outside of Dagger?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably so; I'll modify the default constructors of the Encoder and Decoder as such.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've modified the JacksonEncoder and JacksonDecoder to apply the same default ObjectMapper tweaks that are used in the JacksonModule. |
||
| } | ||
|
|
||
| @Provides(type = Provides.Type.SET_VALUES) Set<Module> noDefaultModules() { | ||
| return Collections.emptySet(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adriancole
Not really a criticism of the pull request (since it appropriately follows the pattern used in the rest of the file), but is there a reason we apply the java plugin in each separate project block rather than in an allprojects or subprojects block?
http://www.gradle.org/docs/current/userguide/multi_project_builds.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android isnt compatible with the java plugin. That said, we don't have an
android module :)