88

i have a class A which has some private fields and the same class extends another class B which also has some private fields which are in class A.

public class A extends B {
    private BigDecimal netAmountTcy;
    private BigDecimal netAmountPcy;   
    private BigDecimal priceTo;  
    private String segment;

    private BigDecimal taxAmountTcy;
    private BigDecimal taxAmountPcy;   
    private BigDecimal tradeFeesTcy;
    private BigDecimal tradeFeesPcy;

// getter and setter for the above fields

}

and class B has got some private fiedls which are in class A

now when i try to create JSON string from above class A i get the following exception :

class com.hexgen.ro.request.A declares multiple JSON fields named netAmountPcy

How to fix this?

Since they are private fields there should not be any problem while creating json string i guess but i am not sure.

i create json string like the following :

Gson gson = new Gson();
 tempJSON = gson.toJson(obj);

here obj is the object of class A

gerrytan
  • 40,313
  • 9
  • 84
  • 99
Java Questions
  • 7,813
  • 41
  • 118
  • 176

11 Answers11

90

Since they are private fields there should not be any problem while creating json string

I don't think this statement is true, GSON looks up at the object's private fields when serializing, meaning all private fields of superclass are included, and when you have fields with same name it throws an error.

If there's any particular field you don't want to include you have to mark it with transient keyword, eg:

private transient BigDecimal tradeFeesPcy;
gerrytan
  • 40,313
  • 9
  • 84
  • 99
  • Thank you it works. But why @Expose annotation of Gson doens't work with supreclass field and it's making confusion ?? – Fakher May 19 '17 at 15:19
  • 2
    @Fakher to get `@Expose` works properly you need to set `@Expose(serialize = false)`and make sure to call `excludeFieldsWithoutExposeAnnotation()`on your `GsonBuilder` object. – Ibrahim.H Feb 12 '18 at 10:49
  • 2
    This comment solved my problem perfectly. Use ```new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(YOUR_OBJECT)```, it works – Eason PI Jul 22 '19 at 04:55
  • if you are working with kotlin there is an annotation for that: ```kotlin /** * Marks the JVM backing field of the annotated property as `transient`, meaning that it is not * part of the default serialized form of the object. */ @Target(FIELD) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented public actual annotation class Transient ``` – AouledIssa Oct 10 '20 at 20:48
77

This is a bit late, but I ran into this exact same problem as well. The only thing was that I wasn't able to modify the superclass as that code wasn't mine. The way that I resolved this was by creating an exclusion strategy that skipped any field that had a field of the same name present in a superclass. Here is my code for that class:

public class SuperclassExclusionStrategy implements ExclusionStrategy
{
    public boolean shouldSkipClass(Class<?> arg0)
    {
        return false;
    }

    public boolean shouldSkipField(FieldAttributes fieldAttributes)
    {
        String fieldName = fieldAttributes.getName();
        Class<?> theClass = fieldAttributes.getDeclaringClass();

        return isFieldInSuperclass(theClass, fieldName);            
    }

    private boolean isFieldInSuperclass(Class<?> subclass, String fieldName)
    {
        Class<?> superclass = subclass.getSuperclass();
        Field field;

        while(superclass != null)
        {   
            field = getField(superclass, fieldName);

            if(field != null)
                return true;

            superclass = superclass.getSuperclass();
        }

        return false;
    }

    private Field getField(Class<?> theClass, String fieldName)
    {
        try
        {
            return theClass.getDeclaredField(fieldName);
        }
        catch(Exception e)
        {
            return null;
        }
    }
}

I then set the Serialization and Deserialization exclusion strategies in the builder as follows:

    builder.addDeserializationExclusionStrategy(new SuperclassExclusionStrategy());
    builder.addSerializationExclusionStrategy(new SuperclassExclusionStrategy());

Hopefully this helps someone!

Adrian Lee
  • 771
  • 6
  • 2
  • 4
    this is the right answer because I don't want to exclude the superclass' object from getting serialized, I just don't want GSON to exclude the extended class' variable either – CQM Feb 28 '14 at 17:07
  • 16
    In my opinion this is the wrong way around. This way your json will contain the variable from the super class, and the value of the subclass will be hidden. One would expect to have it the other way around; have the subclass variable in json, and hide the superclass variable. – Veda Dec 09 '15 at 12:57
  • @Veda, Have you came up with the other way around solution? I'm facing the same situation where I need child variable and value. – learner Mar 22 '18 at 17:03
  • I think to do the inverse you need to know if the type you're looking at is the subtype of another type. It appears to be much more difficult (if it's even possible) to look the other way up the inheritance tree. https://stackoverflow.com/questions/492184/how-do-you-find-all-subclasses-of-a-given-class-in-java – jocull Aug 06 '18 at 20:09
  • The accepted answer works better. If the top class marks the field as transient even though it has the same name as the lower class, it becomes excluded from serialization. – CompEng88 Sep 20 '19 at 21:12
  • well if the top class is something you have as a dependency you can't just easily modify it no? – Miguel Costa Aug 09 '22 at 22:06
20

The same error message also happens if you have different fields, but they have the same @SerializedName.

@SerializedName("date_created")
private Date DateCreated;
@SerializedName("date_created")
private Integer matchTime;

Doing copy/paste you can simply make such mistake. So, look into the the class and its ancestors and check for that.

  1. You cannot have two fields with the same name.
  2. You cannot have two fields with the same serialized name.
  3. Types are irrelevant for these rules.
Gangnus
  • 24,044
  • 16
  • 90
  • 149
  • 2
    What if you my JSON file does have indeed have a same name with different types (on purpose)? ie Some endpoints "main_id" is an Int, some other endpoints "main_id" is a String – Ben Akin Jun 01 '20 at 05:28
  • 1. You cannot have two fields with the same name. 2. You cannot have two fields with the same serialized name. 3. Types are irrelevant in these rules. – Gangnus Jun 01 '20 at 08:48
12

I used GsonBuilder and ExclusionStrategy to avoid the redundant fields as below, it is simple and straight forward.

Gson json = new GsonBuilder()
          .setExclusionStrategies(new ExclusionStrategy() {
             @Override
             public boolean shouldSkipField(FieldAttributes f) {
                if(f.getName().equals("netAmountPcy")){
                   return true;
                }
                return false;
             }

             @Override
             public boolean shouldSkipClass(Class<?> clazz) {
                return false;
             }
          }).create();
Asaph
  • 159,146
  • 25
  • 197
  • 199
Arar
  • 1,926
  • 5
  • 29
  • 47
6

Add following lines at the bottom of proguard.config (if you are using proguard in project)

-keepclassmembers class * {
    private <fields>;    
}
Sujay
  • 601
  • 1
  • 10
  • 19
3

I don't think you should make the members transient, this might lead to errors because members that you might need in the future might be hidden.

How I solved this problem is to use a custom naming strategy and append the full class name to the Json, the downside of this is that it would lead to larger Json and if you need it for something like a Rest Api it would be weird for clients to name the fields that way, but I only needed to serialize to write to disk on android.

So here is an implementation of a custom naming strategy in Kotlin

import com.google.gson.FieldNamingStrategy
import java.lang.reflect.Field

  class GsonFieldNamingStrategy : FieldNamingStrategy {
     override fun translateName(field: Field?): String? {
        return "${field?.declaringClass?.canonicalName}.${field?.name}"
    }
}

So for all fields, the full canonical name would be appended, this would make the child class have a different name from the parent class, but when deserializing, the child class value would be used.

oziomajnr
  • 1,671
  • 1
  • 15
  • 39
3

In kotlin adding the @Transient annotation for the variable on the parent class did the trick for me on a sealed class with open variables.

REM Code
  • 115
  • 6
  • this works in Kotlin for me. thank you – Bob Redity May 27 '22 at 11:57
  • Other answers have more upvotes but this answer gets mine. I had open vals on an abstract base class I was overriding in a sub class. This was confusing Gson. @Transient on the base class open properties fixes it. – whitaay Jul 20 '22 at 21:00
2

In my case I was dumb enough to register an adapter with X class, and try to serialize fromJson with Y class:

final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Game.class, new TournamentSerializer());
final Gson gson = gsonBuilder.create();

createdTournament = gson.fromJson(jsonResponse.toString(), Tournament.class);
Rakesh
  • 4,004
  • 2
  • 19
  • 31
RominaV
  • 3,335
  • 1
  • 29
  • 59
2

Solution for Kotlin, as suggested @Adrian-Lee, you have to tweak some Null Checks

class SuperclassExclusionStrategy : ExclusionStrategy {

    override fun shouldSkipClass(clazz: Class<*>?): Boolean {
        return false
    }

    override fun shouldSkipField(f: FieldAttributes?): Boolean {
        val fieldName = f?.name
        val theClass = f?.declaringClass

        return isFieldInSuperclass(theClass, fieldName)
    }

    private fun isFieldInSuperclass(subclass: Class<*>?, fieldName: String?): Boolean {
        var superclass: Class<*>? = subclass?.superclass
        var field: Field?

        while (superclass != null) {
            field = getField(superclass, fieldName)

            if (field != null)
                return true

            superclass = superclass.superclass
        }

        return false
    }

    private fun getField(theClass: Class<*>, fieldName: String?): Field? {
        return try {
            theClass.getDeclaredField(fieldName)
        } catch (e: Exception) {
            null
        }

    }
}
Rafael Ruiz Muñoz
  • 5,333
  • 6
  • 46
  • 92
1

For Kotlin-er:

val fieldsToExclude = listOf("fieldToExclude", "otherFieldToExclude")

GsonBuilder()
    .setExclusionStrategies(object : ExclusionStrategy {
        override fun shouldSkipField(f: FieldAttributes?) = f?.let { fieldsToExclude.contains(it.name) } ?: false
        override fun shouldSkipClass(clazz: Class<*>?) = false
    })
    .create()
Besnik
  • 6,469
  • 1
  • 31
  • 33
0

if you need subClass "override" superClass same name filed, you can use ExclusionStrategy to remember same name super fileds and ignore them, after done the convert, need clear the thread local temp data.

the ExclusionStrategy

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class SuperClassFieldExcludeStrategy implements ExclusionStrategy {

    ThreadLocal<Set<String>> ignored=ThreadLocal.withInitial(HashSet::new);

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        Class<?> superClass = f.getDeclaringClass().getSuperclass();
        while (superClass!=null && superClass!=Object.class){

            List<Field> superSameFileds = Arrays.stream(superClass.getDeclaredFields()).filter(x -> x.getName().equals(f.getName())).collect(Collectors.toList());
            String superClassName=superClass.getName();
            ignored.get().addAll(superSameFileds.stream().map(x->superClassName+"."+x.getName()).collect(Collectors.toList()));
            superClass=superClass.getSuperclass();
        }

        String name = f.getDeclaringClass().getName()+"."+f.getName();
        return ignored.get().contains(name);
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}

gson creat part

        SuperClassFieldExcludeStrategy superClassFieldExcludeStrategy=new SuperClassFieldExcludeStrategy()
        Gson gson = new GsonBuilder()
                .addSerializationExclusionStrategy(superClassFieldExcludeStrategy)
                .addDeserializationExclusionStrategy(superClassFieldExcludeStrategy)
                .create();

convert part

        try{
            gson.toJson(new Object());
        }finally {
            superClassFieldExcludeStrategy.ignored.remove();            
        }
user2204107
  • 111
  • 1
  • 4