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

@SerializedName bug in Java when it's used with Retrofit2 library #2137

Closed
harrylhp opened this issue Jun 27, 2022 · 5 comments
Closed

@SerializedName bug in Java when it's used with Retrofit2 library #2137

harrylhp opened this issue Jun 27, 2022 · 5 comments
Labels

Comments

@harrylhp
Copy link

harrylhp commented Jun 27, 2022

Gson version

2.9.0

Java / Android version

java 17

Used tools

-Maven wrapper 3.8.4

Description

@SerializedName is not properly serialized in object.The deserialization from URL json to object is ok but not the serialization. If i use @JsonProperty the name is correctly serialized.

Expected behavior

I expect the SerializedName should be used which is "bookNameTest" when I get the return Object but it's not, it's only printed out correctly in the std but if you get the object as return, the response is not correct
@SerializedName("bookNameTest")
private String bookName;

Actual behavior

{
retrofit2.Response response = bookClient.getBook("api/v1/bookService/otherBooks").execute();
return response.body();
}

=> Wrong behavior here, i expect "bookNameTest": "JWT" but it's printed "bookName"="JWT"

{
    "bookId": 1,
    "price": 22.25,
    "bookName": "JWT"
}

Reproduction steps

  1. Create class Book.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private int bookId;
    private double price;

    @SerializedName("bookNameTest")
    private String bookName;
}
  1. Create a GET API
@GetMapping(value = "/bookService/otherBooks", produces = MediaType.APPLICATION_JSON_VALUE)
      public Book getOtherBook(@RequestParam(required = false) String bookID) throws IOException {
      retrofit2.Response<Book> response = bookClient.getBook("api/v1/bookService/otherBooks").execute();
      Gson gson = new GsonBuilder().setPrettyPrinting().create();
      String jsonString = gson.toJson(response.body());
      System.out.println(jsonString);
      System.out.println(response.body());
      return response.body();
}

The mock Json response from URL endpoint "api/v1/bookService/otherBooks"

{
    "bookId": 1,
    "price": 22.25,
    "bookNameTest": "JWT"
}

Please note for bookClient I used Retrofit and use Gson converter. And then the deserialization is correctly done but after that the serialization to use the @SerializedName is not correct.

public bookClient bookClient(){
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .build();

        return new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("/") //can use localhost
                .client(okHttpClient)
                .build().create(bookClient.class);

I have checked the jsonString is printed out correctly from console using System.out.println(jsonString)

{
  "bookId": 1,
  "price": 22.25,
  "bookNameTest": "JWT"
}

but the return response.body() is not
Book(bookId=1, price=22.25, bookName=JWT}

====================================================
If inside Book.java, i used @JsonProperty, the book can be deserialized and serialized correctly. With using the same code, the return object, it will use @JsonProperty name which is "bookNameTest"

import com.fasterxml.jackson.annotation.JsonProperty;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private int bookId;
    private double price;

    @JsonProperty("bookNameTest")
    private String bookName;
    private Author author;
    private Date publishedDate;
}

Correct output

{
    "bookId": 1,
    "price": 22.25,
    "bookNameTest": "JWT"
}

Exception stack trace

@harrylhp harrylhp added the bug label Jun 27, 2022
@Marcono1234
Copy link
Collaborator

Marcono1234 commented Jun 27, 2022

but the return response.body() is not
Book(bookId=1, price=22.25, bookName=JWT}

Is this the issue you are seeing, bookName should be bookNameTest? Or is there something else incorrect?
To me it looks like this output comes from System.out.println(response.body()); which is just using Book.toString(), which has nothing to do with JSON and therefore just contains the field names and is not affected by @SerializedName. Or am I misunderstanding this?

Maybe the problem here is that you have not configured Spring Boot to use Gson, so when your method getOtherBook returns response.body(), Spring Boot uses Jackson to serialize the object to JSON.

@harrylhp
Copy link
Author

harrylhp commented Jun 27, 2022

Hello,
Ya this is the issue i'm seeing, i expect it should be bookNameTest instead of bookName for response.body() in this case for Gson lib (I expect similar behavior as Jackson) and I have configured my SpringBoot to use Gson for object instead of Jackson.

<dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>converter-gson</artifactId>
            <version>2.9.0</version>
        </dependency>

If i replace @SerializedName with @JsonProperty which is using the Jackson Serialization way then I call the respone.body(), i'll get the bookNameTest correctly (even if it's calling the toString method as you mentioned).

But it's not the case for Gson with @SerializedName, the response.body() as you said it will always give me the "bookName". Hope my explanation is clearer :)

@Marcono1234
Copy link
Collaborator

I have configured my SpringBoot to use Gson for object instead of Jackson.

How exactly have you configured Spring Boot? I am not very familiar with it, but tutorials such as this one or this (section "3. Make Gson preferred json mapper") suggest that it might be necessary to adjust the configuration to make Spring Boot prefer Gson over Jackson. Maybe this StackOverflow question is helpful as well.

even if it's calling the toString method as you mentioned

That might be special handling in Lombok for Jackson, but I assume it does not matter for the JSON serialization.

@harrylhp
Copy link
Author

Hi Marco
For me to force using Gson, i have defined the converter inside the Client before sending http Request and it's using Gson. Else if i define to use .addConverterFactory(JacksonConverterFactory.create()), the Gson initialization will fail so i'm pretty sure my SpringBoot is using Gson at the time of converting.

public bookClient bookClient(){
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .build();

        return new Retrofit.Builder()
                .**addConverterFactory(GsonConverterFactory.create())**
                .baseUrl("/") //can use localhost
                .client(okHttpClient)
                .build().create(bookClient.class);

For Lombok, i have checked the generated classes toString for both @SerializedName and @JsonProperty i don't see any dfiference. So it's kind of confusing me for the Gson behavior, as you could see the Serialized String from println is correct, but if calling the response.body() to get Object, it's not as I expected the library will behave

@harrylhp
Copy link
Author

Hi Marco, i have found the issue, it's from SpringBoot configuration. I need to add a new config inside application.yml to force it to use Gson

spring
  mvc:
    converters:
      preferred-json-mapper: gson

Thank for your time. I think i'll close the issue as it's not a bug it's SpringBoot configuration :)

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

No branches or pull requests

2 participants