类 A 声明多个 JSON 字段

我有一个类 A,它有一些私有字段,同一个类扩展了另一个类 B,它也有一些私有字段在类 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


}

B 类有一些在 A 类中的私有领地

现在,当我尝试从上面的类 A 中创建 JSON 字符串时,我得到了下面的例外:

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

怎么补救?

因为他们是私人领域应该没有任何问题,而创建 json 字符串我想,但我不确定。

我像下面这样创建 json 字符串:

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

这里 obj 是类 A 的对象

109078 次浏览

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;

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!

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

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.

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

-keepclassmembers class * {
private <fields>;
}

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
}


}
}

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.

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

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

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.