【问题标题】:class A declares multiple JSON fieldsA 类声明了多个 JSON 字段
【发布时间】:2013-05-04 19:10:24
【问题描述】:

我有一个 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类的对象

【问题讨论】:

  • 发布您的超级 B 类。

标签: java json gson


【解决方案1】:

由于它们是私有字段,因此在创建 json 字符串时应该没有任何问题

我不认为这个说法是正确的,GSON 在序列化时会查找对象的私有字段,这意味着包含超类的所有私有字段,并且当您有同名的字段时会引发错误。

如果您不想包含任何特定字段,则必须使用 transient 关键字对其进行标记,例如:

private transient BigDecimal tradeFeesPcy;

【讨论】:

  • 谢谢它有效。但是为什么 Gson 的 @Expose 注释不适用于超类字段并且它会造成混乱??
  • @Fakher 要让@Expose 正常工作,您需要设置@Expose(serialize = false) 并确保在您的GsonBuilder 对象上调用excludeFieldsWithoutExposeAnnotation()
  • 这条评论完美地解决了我的问题。使用new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(YOUR_OBJECT),它可以工作
  • 如果您正在使用 kotlin,则有一个注释: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
【解决方案2】:

这有点晚了,但我也遇到了同样的问题。唯一的问题是我无法修改超类,因为该代码不是我的。我解决这个问题的方法是创建一个排除策略,该策略跳过在超类中存在同名字段的任何字段。这是我的那个类的代码:

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

然后我在构建器中设置序列化和反序列化排除策略如下:

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

希望这对某人有所帮助!

【讨论】:

  • 这是正确的答案,因为我不想将超类的对象排除在序列化之外,我只是不想让 GSON 排除扩展类的变量
  • 在我看来这是错误的方法。这样,您的 json 将包含来自超类的变量,而子类的值将被隐藏。人们会期望相反。在json中有子类变量,隐藏超类变量。
  • @Veda,你有没有想出另一种解决方案?我面临同样的情况,我需要子变量和值。
  • 我认为相反,您需要知道您正在查看的类型是否是另一种类型的子类型。从继承树的另一个方向看似乎要困难得多(如果可能的话)。 stackoverflow.com/questions/492184/…
  • 接受的答案效果更好。如果顶级类将字段标记为瞬态,即使它与低级类同名,它也会被排除在序列化之外。
【解决方案3】:

如果您有不同的字段,但它们具有相同的@SerializedName,也会出现相同的错误消息。

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

进行复制/粘贴您很容易犯这样的错误。因此,请查看类 及其祖先 并检查。

  1. 您不能有两个同名的字段。
  2. 您不能有两个具有相同序列化名称的字段。
  3. 类型与这些规则无关。

【讨论】:

  • 如果我的 JSON 文件确实具有不同类型的相同名称(故意)怎么办?即一些端点“main_id”是一个Int,一些其他端点“main_id”是一个String
  • 1.您不能有两个具有相同名称的字段。 2. 不能有两个具有相同序列化名称的字段。 3. 类型与这些规则无关。
【解决方案4】:

在 proguard.config 的底部添加以下行 (如果你在项目中使用 proguard)

-keepclassmembers class * {
    private <fields>;    
}

【讨论】:

    【解决方案5】:

    我使用GsonBuilderExclusionStrategy来避免下面的冗余字段,简单直接。

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

    【讨论】:

    • 此解决方案无需修改父类和子类中的字段即可工作。
    【解决方案6】:

    在我的例子中,我愚蠢到用 X 类注册一个适配器,并尝试用 Y 类序列化 fromJson:

    final GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Game.class, new TournamentSerializer());
    final Gson gson = gsonBuilder.create();
    
    createdTournament = gson.fromJson(jsonResponse.toString(), Tournament.class);
    

    【讨论】:

      【解决方案7】:

      Kotlin 的解决方案,正如 @Adrian-Lee 建议的那样,您必须调整一些 Null 检查

      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
              }
      
          }
      }
      

      【讨论】:

        【解决方案8】:

        我认为您不应该让成员成为临时成员,这可能会导致错误,因为您将来可能需要的成员可能会被隐藏。

        我解决这个问题的方法是使用自定义命名策略并将完整的类名附加到 Json,这样做的缺点是它会导致更大的 Json,如果你需要它来处理像 Rest Api 这样的东西,它会客户端以这种方式命名字段会很奇怪,但我只需要序列化即可在 android 上写入磁盘。

        所以这是 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}"
            }
        }
        

        因此对于所有字段,将附加完整的规范名称,这将使子类与父类具有不同的名称,但在反序列化时,将使用子类值。

        【讨论】:

        • 也许这结合@Adrian 的解决方案应该可以工作
        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

          在 kotlin 中,为父类上的变量添加 @Transient 注释对我来说在具有开放变量的 sealed 类上起到了作用。

          【讨论】:

            猜你喜欢
            • 2021-02-13
            • 1970-01-01
            • 2017-12-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-05-13
            • 1970-01-01
            • 2016-01-25
            相关资源
            最近更新 更多