Klasse A deklariert mehrere JSON-Felder

74

Ich habe eine Klasse A, die einige private Felder hat und die gleiche Klasse erweitert eine andere Klasse B, die auch einige private Felder hat, die in Klasse A sind.

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

}

und Klasse B hat einige private Fiedls, die in Klasse A sind

Wenn ich jetzt versuche, eine JSON-Zeichenfolge aus der obigen Klasse A zu erstellen, erhalte ich die folgende Ausnahme:

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

Wie kann ich das beheben?

Da es sich um private Felder handelt, sollte es beim Erstellen eines JSON-Strings kein Problem geben, aber ich bin mir nicht sicher.

Ich erstelle einen JSON-String wie folgt:

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

hier ist obj das Objekt der Klasse A.

Java-Fragen
quelle
1
poste

Antworten:

79

Da es sich um private Felder handelt, sollte es beim Erstellen eines JSON-Strings keine Probleme geben

Ich glaube nicht, dass diese Aussage wahr ist. GSON überprüft beim Serialisieren die privaten Felder des Objekts, was bedeutet, dass alle privaten Felder der Oberklasse enthalten sind. Wenn Sie Felder mit demselben Namen haben, wird ein Fehler ausgegeben.

Wenn Sie ein bestimmtes Feld nicht einschließen möchten, müssen Sie es mit einem transientSchlüsselwort markieren , z.

private transient BigDecimal tradeFeesPcy;
Gerrytan
quelle
Danke, dass es funktioniert. Aber warum funktioniert die @ Expose-Annotation von Gson nicht mit dem Feld der Oberklasse und es macht Verwirrung?
Fakher
2
@Fakher, damit es @Exposerichtig funktioniert, müssen Sie festlegen @Expose(serialize = false)und sicherstellen, dass Sie excludeFieldsWithoutExposeAnnotation()Ihr GsonBuilderObjekt aufrufen .
Ibrahim.H
1
Dieser Kommentar hat mein Problem perfekt gelöst. Verwenden Sie new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(YOUR_OBJECT), es funktioniert
Eason PI
Wenn Sie mit Kotlin arbeiten, gibt es dafür eine Anmerkung: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
66

Das ist etwas spät, aber ich bin auch auf genau das gleiche Problem gestoßen. Das einzige war, dass ich die Superklasse nicht ändern konnte, da dieser Code nicht meiner war. Ich habe dieses Problem gelöst, indem ich eine Ausschlussstrategie erstellt habe, bei der alle Felder übersprungen wurden, in denen ein gleichnamiges Feld in einer Oberklasse vorhanden war. Hier ist mein Code für diese Klasse:

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

Anschließend habe ich die Ausschlussstrategien für Serialisierung und Deserialisierung im Builder wie folgt festgelegt:

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

Hoffentlich hilft das jemandem!

Adrian Lee
quelle
3
Dies ist die richtige Antwort, da ich das Objekt der Oberklasse nicht von der Serialisierung ausschließen möchte. Ich möchte nur nicht, dass GSON auch die Variable der erweiterten Klasse ausschließt
CQM
13
Meiner Meinung nach ist dies der falsche Weg. Auf diese Weise enthält Ihr JSON die Variable aus der Superklasse und der Wert der Unterklasse wird ausgeblendet. Man würde erwarten, dass es umgekehrt ist; Haben Sie die Unterklassenvariable in json und verbergen Sie die Oberklassenvariable.
Veda
@Veda, hast du eine umgekehrte Lösung gefunden? Ich stehe vor der gleichen Situation, in der ich untergeordnete Variablen und Werte benötige.
Lerner
Ich denke, um das Gegenteil zu tun, müssen Sie wissen, ob der Typ, den Sie betrachten, der Subtyp eines anderen Typs ist. Es scheint viel schwieriger zu sein (wenn es überhaupt möglich ist), im Vererbungsbaum nach oben zu schauen. stackoverflow.com/questions/492184/…
jocull
Die akzeptierte Antwort funktioniert besser. Wenn die oberste Klasse das Feld als vorübergehend markiert, obwohl es denselben Namen wie die untere Klasse hat, wird es von der Serialisierung ausgeschlossen.
CompEng88
15

Die gleiche Fehlermeldung tritt auch auf, wenn Sie unterschiedliche Felder haben, diese jedoch gleich sind @SerializedName.

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

Beim Kopieren / Einfügen können Sie einfach einen solchen Fehler machen. Schauen Sie sich also die Klasse und ihre Vorfahren an und prüfen Sie, ob dies der Fall ist.

Gangnus
quelle
1
Was ist, wenn meine JSON-Datei tatsächlich denselben Namen mit unterschiedlichen Typen hat (absichtlich)? dh Einige Endpunkte "main_id" ist ein Int, andere Endpunkte "main_id" ist ein String
Ben Akin
1. Sie können nicht zwei Felder mit demselben Namen haben. 2. Sie können nicht zwei Felder mit demselben serialisierten Namen haben. 3. Typen sind in diesen Regeln irrelevant.
Gangnus
7

Fügen Sie die folgenden Zeilen am Ende von proguard.config hinzu (wenn Sie proguard im Projekt verwenden).

-keepclassmembers class * {
    private <fields>;    
}
Sujay
quelle
2

In meinem Fall war ich dumm genug, einen Adapter bei der X-Klasse zu registrieren und zu versuchen, von Json mit der Y-Klasse zu serialisieren:

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

createdTournament = gson.fromJson(jsonResponse.toString(), Tournament.class);
RominaV
quelle
2

Ich habe GsonBuilderund benutztExclusionStrategy um die redundanten Felder wie unten zu vermeiden, ist es einfach und unkompliziert.

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();
Saif Masadeh
quelle
Diese Lösung funktioniert ohne Änderung der Felder in der übergeordneten und untergeordneten Klasse.
Jennifer
1

Lösung für Kotlin, wie bei Adrian-Lee vorgeschlagen, müssen Sie einige Null-Checks optimieren

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
quelle
0

Ich denke nicht, dass Sie die Mitglieder vorübergehend machen sollten, dies könnte zu Fehlern führen, da Mitglieder, die Sie möglicherweise in Zukunft benötigen, möglicherweise ausgeblendet sind.

Ich habe dieses Problem gelöst, indem ich eine benutzerdefinierte Namensstrategie verwendet und den vollständigen Klassennamen an den Json angehängt habe. Der Nachteil ist, dass dies zu einem größeren Json führen würde und wenn Sie ihn für so etwas wie eine Rest-API benötigen, wäre es seltsam Clients, um die Felder so zu benennen, aber ich musste nur serialisieren, um auf die Festplatte auf Android zu schreiben.

Hier ist eine Implementierung einer benutzerdefinierten Namensstrategie 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}"
    }
}

Für alle Felder würde also der vollständige kanonische Name angehängt. Dies würde dazu führen, dass die untergeordnete Klasse einen anderen Namen als die übergeordnete Klasse hat. Beim Deserialisieren wird jedoch der Wert der untergeordneten Klasse verwendet.

oziomajnr
quelle
Vielleicht sollte dies in Kombination mit @ Adrians Lösung funktionieren
Gavriel