Java Questions
Java Questions

Reputation: 7953

class A declares multiple JSON fields

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

Upvotes: 91

Views: 129957

Answers (11)

user2204107
user2204107

Reputation: 190

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();            
        }

Upvotes: 0

Gangnus
Gangnus

Reputation: 24464

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.

Upvotes: 21

REM Code
REM Code

Reputation: 165

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.

Upvotes: 8

Besnik
Besnik

Reputation: 6529

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()

Upvotes: 1

Arar
Arar

Reputation: 2066

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();

Upvotes: 13

RominaV
RominaV

Reputation: 3415

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);

Upvotes: 2

oziomajnr
oziomajnr

Reputation: 1741

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.

Upvotes: 4

Rafael Ruiz Mu&#241;oz
Rafael Ruiz Mu&#241;oz

Reputation: 5472

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
        }

    }
}

Upvotes: 2

Sujay
Sujay

Reputation: 601

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

-keepclassmembers class * {
    private <fields>;    
}

Upvotes: 6

Adrian Lee
Adrian Lee

Reputation: 781

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!

Upvotes: 78

gerrytan
gerrytan

Reputation: 41133

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;

Upvotes: 93

Related Questions