14

I'm working on an app that is using Gson as JSON deserializer and needs to deserialize polymorphic JSON from REST API. Before explaining mi issue note that I've already been looking around polymorphic deserialization with Gson and have implemented it on several cases successfully. So this is a specific issue I'm having. Also I've read this great post and this Stack Overflow discussion before asking this. I'm using RuntimeTypeAdapterFactory to deserialize polymorphic objects by the way.

The problem I'm having is that apparently GSON's RuntimeTypeAdapterFactory does't allow to declare the field which specifies the type of the object inside the hierarchy structure. I'll explain further with some code. I have the following pojos structure (pojos had been reduced for the sake of simplicity):

public abstract class BaseUser {
    @Expose
    protected EnumMobileUserType userType; 
}


public class User extends BaseUser {
    @Expose
    private String name;
    @Expose
    private String email;     
}

public class RegularUser extends User {
    @Expose
    private String address;    
}

public class SpecialUser extends User {
    @Expose
    private String promoCode;
}

Now this is the code where I defined the RuntimeTypeAdapterFactory for User hierarchy.

public static RuntimeTypeAdapterFactory<BaseUser> getUserTypeAdapter() {
   return RuntimeTypeAdapterFactory
        .of(BaseUser.class, "userType")
        .registerSubtype(User.class, EnumMobileUserType.USER.toString())
        .registerSubtype(RegularUser.class, EnumMobileUserType.REGULAR.toString())
        .registerSubtype(SpecialUser.class, EnumMobileUserType.SPECIAL.toString());
}

public static Gson getGsonWithTypeAdapters() {
    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapterFactory(getUserTypeAdapter());
    return builder.create();
}

Now when I try to deserialize a JSON text:

{  
   "user":{  
      "userType":"USER",
      "email":"albert@gmail.com",
      "name":"Albert"
   }
}

I get this exception:

com.google.gson.JsonParseException: cannot serialize com.mobile.model.entities.v2.common.User because it already defines a field named userType

But if I change the name of the property "userType" in my BaseUser class to "type" for example and I deserialize the same JSON everything works properly. I don't get why Gson RuntimeTypeAdapterFactory has this restriction. In fact in this blog post apparently this is not an issue.

Can anyone explain what's going on here, why the name of the property that defines the type cannot be defined inside the pojos hierarchy?

EDIT the issue is not when deserializing but when serializing using the code described above. Find further explanation in the answer.

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
JorgeMuci
  • 658
  • 9
  • 22
  • As per the error, are you sure you haven't declared the field `userType` in the `User` class also? It is already declared in the 'BaseUser' class so no need to redeclare it. – Jyotman Singh Sep 30 '16 at 13:56
  • Hi Jyotman. No, I've made sure I'm not declaring the field usertype twice. It's only declared on the base user. Plus I said at the end of question, the deserialization is working fine as soon as change the name of the field userType from the BaseUser class to something different than what is declared on the json and the RuntimeTypeAdapterFactory. But thanks for the suggestion! – JorgeMuci Oct 01 '16 at 14:08

4 Answers4

7

Well, after some time digging I found out that the problem is not actually deserializing, the problem comes when serializing and having a RuntimeTypeFactory registered as described in the question. If you register a runtimeTypeAdapterFactory and use the same field name to define the class type in the factory and in your pojo, json resulting from serializing pojo to json using GSON with the RuntimeTypeAdapterFactory for a SpecialUser for example will be:

{  
  "user":{  
      "userType":"SPECIAL",
      "email":"albert@gmail.com",
      "name":"Albert"
      "userType":"SPECIAL"
  }
}

This will result in the exception described:

com.google.gson.JsonParseException: cannot serialize com.mobile.model.entities.v2.common.User because it already defines a field named userType

because de field userType is repeated in the json due to GSON serializer, which will automatically add a field as declared in the RuntimeTypeAdapterFactory registered for the class BaseUser.

JorgeMuci
  • 658
  • 9
  • 22
  • 2
    To fix this, use the `transient` modifier on field `userType`. This will make it so that normal GSON serialization omits it, but then the `RuntimeTypeFactory` adds it like it was meant to. – JamMaster Jul 01 '18 at 12:32
  • Do this `data SpecialUser(name: String, email: String): User("specialUser)` instead – Qamar Mar 09 '23 at 11:01
2

I think that using your own userType without @Expose annotation will do the trick

Regads

javiMit
  • 41
  • 4
1

You can always use a default Gson instance to serialize (new Gson()) and then use the RuntimeTypeAdapterFactory instance to deserialize.

It is recommended not to use @Expose if you want everything to be transformed. It only blows up your model classes with redundant annotations.

Jeffrey
  • 1,998
  • 1
  • 25
  • 22
0

I had this same problem working on android/kotlin, JorgeMuci's answer didn't work for me, but using the same approach, I used @Transient tag and it worked perfect

Juan Gil
  • 37
  • 7