2016-08-19 4 views
0

Im Folgenden ist das Klassendiagramm der Problemdomäne. Wir haben JSON decodierte Nachrichten mit unterschiedlicher Semantik, die verschiedene Methoden im Code (initialisieren, aktualisieren) für verschiedene Sichten auslösen.Gson deserialize polymorphes Mitglied Listenvariable ist null

Die Message deserialisiert in feine entweder InitMessage oder DataMessage mit der Lösung here eine RuntimeTypeAdapterFactory mit vorgeschlagen und alle möglichen Subtypen Registrierung. Die Liste DataMessage.value ist jedoch leer (nicht deserialisiert). Das Problem ist das verschachtelte polymorphe Element in DataMessage.

Class diagram of the problem domain

Die Adapter Fabriken:

RuntimeTypeAdapterFactory<Message> messageAdapterFactory = RuntimeTypeAdapterFactory 
    .of(Message.class, "MESSAGE_TYPE") 
    .registerSubtype(InitializationMessage.class, "INIT") 
    .registerSubtype(DataMessage.class, "DATA"); 

RuntimeTypeAdapterFactory<DataValue> dataAdapterFactory = RuntimeTypeAdapterFactory 
    .of(DataValue.class, "NAME") 
    .registerSubtype(DataValueA.class, "A") 
    .registerSubtype(DataValueB.class, "B") 
    .registerSubtype(DataValueC.class, "C"); 

Die Erstellung der Nachricht:

TypeToken<Message> typeToken = new TypeToken<Message>() {}; 
Message msg = gson.fromJson(json, typeToken.getType()); 

Datamessage-Klasse:

public class DataMessage extends Message { 

    private List<DataValue> value; 

    public List<DataValue> getValue() { 
    return value; 
    } 

    public void setValue(List<DataValue> value) { 
    this.value= value; 
    } 
} 

D ataValueA Klasse:

public class DataValueA extends DataValue { 

    private Map<String, Float> value; 

    public float getValue(String location) { 
    return value.get(location); 
    } 
} 

Die entsprechende JSON:

{ 
    "MESSAGE_TYPE" : "DATA", 
    "VALUE" : [ 
    { 
     "NAME" : "C", 
     "VALUE" : 1.3 
    }, 
    { 
     "NAME" : "A", 
     "VALUE" : { 
      "FL" : 18.4, 
      "FR" : 18.4, 
      "RL" : 18.4, 
      "RR" : 18.4 
     } 
    }] 
} 

möchte ich die DataValue in ihre jeweiligen Unterklasse deserialisiert werden (DataValueA ...).

+0

Einen 'JsonDeserializer' für' Message' schreiben, der die korrekten Unterklassen erstellt und weitere Deserialisierung über 'context' delegiert. deserialize (dataObject, DataValue.class) 'gibt mir die Möglichkeit, polymorphe Nachrichten mit den polymorphen Daten zu deserialisieren. Jedoch verliere ich vollständig die Vorteile in lesbarem und wartbarem Code der Datenbindung, die mit "RuntimeTypeAdapterFactory" bereitgestellt wird. – lschuetze

Antwort

1

Eine Lösung besteht darin, die GsonBuilder.registerTypeAdapter-Methode zu verwenden, um benutzerdefinierte zu registrieren. Der Weg besteht darin, ein Feld in der Nachricht zu verwenden, um zu definieren, welche Unterklasse erstellt wird (genau wie mit RuntimeTypeAdapterFactory, die nicht standardmäßig ausgeliefert wird und in gson-extra lebt).

Der Deserializer wird für jede abstrakte Oberklasse registriert.

gson = new GsonBuilder() 
    .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 
    .registerTypeAdapter(Message.class, new MessageAdapter()) 
    .registerTypeAdapter(DataValue.class, new DataValueAdapter()) 
    .create(); 

Da das Feld Subtypen zu unterscheiden ist Name genannt Sie die deserialize Funktion wie folgt definieren können. Es gibt eine Zuordnung vom Inhalt des Feldes zur jeweiligen Unterklasse.

public class DataValueAdapter implements JsonDeserializer<DataValue> { 
    private final static Map<String, Class<?>> FieldToClass; 

    static { 
    FieldToClass = new HashMap<>(); 
    FieldToClass.put("PERFORMANCE", PerformanceDataValue.class); 
    FieldToClass.put("TIRE_SLIP", TireSlipDataValue.class); 
    } 

    @Override 
    public DataValue deserialize(JsonElement json, Type typeOfT, 
           JsonDeserializationContext context) throws JsonParseException { 
    JsonObject jsonObject = json.getAsJsonObject(); 
    String dataType = jsonObject.get("NAME").getAsString(); 
    return context.deserialize(json, FieldToClass.get(dataType)); 
    } 
} 

Um die reflektierende Deserializer zu machen (die für die Unterklassen verwendet werden, solange Sie mit dem Standard-Deserializer in Ordnung sind) arbeiten die Subklassen @SerializedName an den Eigenschaften angeben müssen. Ohne es hat nicht für mich funktioniert.

Verwandte Themen