Skip to content
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

Update to Gson 2.4, use newJsonWriter in GsonConverterFactory #1062

Closed
tomkoptel opened this issue Sep 7, 2015 · 5 comments
Closed

Update to Gson 2.4, use newJsonWriter in GsonConverterFactory #1062

tomkoptel opened this issue Sep 7, 2015 · 5 comments
Milestone

Comments

@tomkoptel
Copy link

Hello guys!

Recently I found out issue with null fields serialization during request call process. It is more looks like issue of Gson and there is pretty easy workaround of issue. So far issue lies within GsonConverterFactory which internally uses TypeAdapter instead of actual gson instance. I am not sure what was intent of using TypeAdapter, but such approach ignores extra configurations of Gson object such as serializaeNulls: false.

Below is sample of code. Comment points out issue.

    class Data {
        private final String tag;
        private String optionalTag;

        private Data(@NonNull String tag) {
            this.tag = tag;
        }

        public void setOptionalTag(@Nullable String optionalTag) {
            this.optionalTag = optionalTag;
        }

        @NonNull
        public String getTag() {
            return tag;
        }

        @Nullable
        public String getOptionalTag() {
            return optionalTag;
        }
    }

    interface FooService {
        @POST("v1/something/create")
        Call<Response<Object>> barEndpoint(@Body Data data);
    }
   @Test
    public void test() {
        Gson gson = new GsonBuilder().create();
        Converter.Factory converterFactory = GsonConverterFactory.create(gson);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://any.api/")
                .addConverterFactory(converterFactory)
                .build();
        Data data = new Data("my tag");
        FooService service = retrofit.create(FooService.class);

        // Expected result of serialization is {"tag": "my tag"}
        // Actual result of serialization is {"tag": "my tag", "optionalTag": null}
        service.barEndpoint(data);
   }

Suggested fix could be to use gson instance and not TypeAdapter.

class GsonConverterFactory implements Converter.Factory {
    /**
     * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static GsonConverterFactory create() {
        return create(new Gson());
    }

    /**
     * Create an instance using {@code gson} for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static GsonConverterFactory create(Gson gson) {
        return new GsonConverterFactory(gson);
    }

    private final Gson gson;

    private GsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    /** Create a converter for {@code type}. */
    @Override public Converter<?> get(Type type) {
        return new GsonConverter<>(type, gson);
    }

    private static class GsonConverter<T> implements Converter<T> {
        private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
        private static final Charset UTF_8 = Charset.forName("UTF-8");

        private final Gson mGson;
        private final Type mType;

        DefaultGsonConverter(Type type, Gson gson) {
            mType = type;
            mGson = gson;
        }

        @Override public T fromBody(ResponseBody body) throws IOException {
            Reader in = body.charStream();
            try {
                return mGson.fromJson(in, mType);
            } finally {
                try {
                    in.close();
                } catch (IOException ignored) {
                }
            }
        }

        @Override public RequestBody toBody(T value) {
            Buffer buffer = new Buffer();
            Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
            try {
                mGson.toJson(value, mType, writer);
                writer.flush();
            } catch (IOException e) {
                throw new AssertionError(e); // Writing to Buffer does no I/O.
            }
            return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
        }
    }
}
@JakeWharton
Copy link
Collaborator

Gson was changed to expose this property (google/gson#700) so once it has its next release (this month or next, well before our v2.0) we can update to configuring a JsonWriter to serialize nulls based on the user configuration.

@JakeWharton
Copy link
Collaborator

Waiting on 2.4 release which we're pushing for this coming week: google/gson#668. If it happens quick enough I can squeeze this into beta2!

@JakeWharton JakeWharton changed the title GsonConverterFactory should use Gson instance not TypeAdapter Gson 2.4, use newJsonWriter in GsonConverterFactory Sep 26, 2015
@JakeWharton JakeWharton changed the title Gson 2.4, use newJsonWriter in GsonConverterFactory Update to Gson 2.4, use newJsonWriter in GsonConverterFactory Sep 26, 2015
@Turbo87
Copy link
Contributor

Turbo87 commented Sep 27, 2015 via email

@tomkoptel
Copy link
Author

Great, thank you!

@luhuanxml
Copy link

I can't speak English well, I have a question.
like this:
status:"1",msg:"success",data:{}
but:
status:"0",msg:"error",data""

How to solve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants