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

GsonBuilder backwards compatibility broken, throws "Cannot override built-in adapter" error #2787

Open
1 of 4 tasks
jirijanak opened this issue Jan 8, 2025 · 3 comments
Open
1 of 4 tasks
Labels

Comments

@jirijanak
Copy link

Gson version

2.11

Java / Android version

Semeru JDK 21.0.5

Used tools

  • Maven; version:
  • Gradle; version: 8.12
  • ProGuard (attach the configuration file please); version:
  • ...

Description

We are currently maintaining a legacy application in an ongoing support mode. In order to address security vulnerabilities, we needed to update several third-party dependencies, which required upgrading to Gson version 2.11. However, after the update, another dependency, com.cloudant:cloudant-client:2.20.1 (https://github.com/cloudant/java-cloudant), began to fail. This dependency worked correctly with Gson 2.10.1 but now fails with Gson 2.11. The error encountered is as follows:

java.lang.IllegalArgumentException: Cannot override built-in adapter for class com.google.gson.JsonObject
 	at com.google.gson.GsonBuilder.registerTypeAdapter(GsonBuilder.java:714) ~[gson-2.11.0.jar:?]
 	at com.cloudant.client.org.lightcouch.internal.GsonHelper.initGson(GsonHelper.java:38) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.org.lightcouch.CouchDbClient.<init>(CouchDbClient.java:95) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.org.lightcouch.CouchDbClient.<init>(CouchDbClient.java:145) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.api.CloudantClient.<init>(CloudantClient.java:141) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.api.ClientBuilder.build(ClientBuilder.java:396) ~[cloudant-client-2.20.1.jar:?]

The issue appears to be related to how GsonBuilder is used in the method defined in https://github.com/cloudant/java-cloudant/blob/master/cloudant-client/src/main/java/com/cloudant/client/org/lightcouch/internal/GsonHelper.java

    public static GsonBuilder initGson(GsonBuilder gsonBuilder) {
        gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonDeserializer<JsonObject>() {
            public JsonObject deserialize(JsonElement json,
                                          Type typeOfT, JsonDeserializationContext context)
                    throws JsonParseException {
                return json.getAsJsonObject();
            }
        });
        gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonSerializer<JsonObject>() {
            public JsonElement serialize(JsonObject src, Type typeOfSrc,
                                         JsonSerializationContext context) {
                return src.getAsJsonObject();
            }

        });

        return gsonBuilder;
    }

It seems that something has changed in GsonBuilder between versions 2.10.1 and 2.11 that is now causing a conflict and breaking backward compatibility. Could you clarify whether this is a bug, or is it the intended behavior in the new Gson version?

Expected behavior

Considering this was minor version update, I would expect that GsonBuilder would continue to work as it was.

Actual behavior

It seems that something has changed in GsonBuilder between versions 2.10.1 and 2.11 that is now causing a conflict and breaking backward compatibility.

Reproduction steps

https://github.com/cloudant/java-cloudant/blob/master/cloudant-client/src/main/java/com/cloudant/client/org/lightcouch/internal/GsonHelper.java should still be able to register TypeAdapter in GsonBuilder.

NOTE: I can't change com.cloudant:cloudant-client:2.20.1 code. I know that it's deprecated, sunset and no longer supported by any team - but to replace it with some other library is involved change. Therefore I am trying to rather ask if backward compatibility of GsonBuilder could be kept.

Exception stack trace

java.lang.IllegalArgumentException: Cannot override built-in adapter for class com.google.gson.JsonObject
 	at com.google.gson.GsonBuilder.registerTypeAdapter(GsonBuilder.java:714) ~[gson-2.11.0.jar:?]
 	at com.cloudant.client.org.lightcouch.internal.GsonHelper.initGson(GsonHelper.java:38) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.org.lightcouch.CouchDbClient.<init>(CouchDbClient.java:95) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.org.lightcouch.CouchDbClient.<init>(CouchDbClient.java:145) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.api.CloudantClient.<init>(CloudantClient.java:141) ~[cloudant-client-2.20.1.jar:?]
 	at com.cloudant.client.api.ClientBuilder.build(ClientBuilder.java:396) ~[cloudant-client-2.20.1.jar:?]
@jirijanak jirijanak added the bug label Jan 8, 2025
@Marcono1234
Copy link
Collaborator

Could you clarify whether this is a bug, or is it the intended behavior in the new Gson version?

This change was intentional, see #2479 and #2436. The built-in adapter for JsonElement and its subclasses such as JsonObject cannot be overridden, trying to register an adapter them has no effect1. And it has been that way for a long time probably see bc68d72. In Gson 2.11.0 only additional validation was added to detect and prevent this incorrect configuration.

It seems for java-cloudant that GsonBuilder code was added in the class CouchDbClientBase.java in cloudant/java-cloudant@15093a1 and cloudant/java-cloudant@90a5244. Not sure if back then Gson behaved differently.
Though I am also not completely sure what the intention of this code is: It registers serializers and deserializers for JsonObject which just call getAsJsonObject() and that just returns this (even back then).

Maybe I am overlooking some corner case though, where this code in java-cloudant did / does actually make a difference.

Otherwise, maybe one case where it is kind of possible to override the JsonElement adapter is when using Gson#getDelegateAdapter in some weird unintended (?) way, e.g. passing skipPast: this but instead of using type: type (the TypeAdapterFactory#create parameter), using an unrelated TypeToken<JsonObject> or similar.

Footnotes

  1. You might find code out there which registers adapters for JSONArray and JSONObject. Those are not Gson's own classes but instead are org.json.* classes, so such code does actually have an affect.

@Marcono1234
Copy link
Collaborator

A GitHub search showed a few other cases of registerTypeAdapter(JsonObject.class in other projects, but based on the Git history, the intention is often not obvious (because it is mixed with other changes), the closest I found might be MassiveCraft/MassiveCore@f4b3924.
So maybe Gson did behave differently in some old versions?

Maybe this as done as workaround for #362 (fixed by 65df3b9 in Gson 2.2.3); it seems back then no built-in adapter for JsonElement had been registered, so it was necessary to manually register an adapter which just returned the given JsonElement.

@Marcono1234
Copy link
Collaborator

@eamonnmcmanus, what do you think?

Should we make these GsonBuilder methods more lenient again? Or at least permit usage of JsonSerializer and JsonDeserializer for JsonElement and subclasses, assuming it was done indeed as workaround for #362 (even if nowadays these serializers and deserializers likely have no effect).

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