2013-05-06 11 views
18

Ich arbeite an einigen Server-Code, wo der Client Anforderungen in Form von JSON sendet. Mein Problem ist, dass es eine Anzahl von möglichen Anfragen gibt, die alle in kleinen Implementierungsdetails variieren. ich daher gedacht, um eine Anfrage-Schnittstelle zu verwenden, wie folgt definiert:Verwenden von Gson mit Interface-Typen

public interface Request { 
    Response process (); 
} 

Von dort habe ich die Schnittstelle in einer Klasse implementiert namens LoginRequest wie gezeigt:

public class LoginRequest implements Request { 
    private String type = "LOGIN"; 
    private String username; 
    private String password; 

    public LoginRequest(String username, String password) { 
     this.username = username; 
     this.password = password; 
    } 

    public String getType() { 
     return type; 
    } 
    public void setType(String type) { 
     this.type = type; 
    } 
    public String getUsername() { 
     return username; 
    } 
    public void setUsername(String username) { 
     this.username = username; 
    } 
    public String getPassword() { 
     return password; 
    } 
    public void setPassword(String password) { 
     this.password = password; 
    } 

    /** 
    * This method is what actually runs the login process, returning an 
    * appropriate response depending on the outcome of the process. 
    */ 
    @Override 
    public Response process() { 
     // TODO: Authenticate the user - Does username/password combo exist 
     // TODO: If the user details are ok, create the Player and add to list of available players 
     // TODO: Return a response indicating success or failure of the authentication 
     return null; 
    } 

    @Override 
    public String toString() { 
     return "LoginRequest [type=" + type + ", username=" + username 
      + ", password=" + password + "]"; 
    } 
} 

mit JSON zu arbeiten, habe ich eine GsonBuilder Instanz und registriert eine InstanceCreator wie gezeigt:

public class LoginRequestCreator implements InstanceCreator<LoginRequest> { 
    @Override 
    public LoginRequest createInstance(Type arg0) { 
     return new LoginRequest("username", "password"); 
    } 
} 

die ich dann verwendet, wie in das schnipsel unten:

GsonBuilder builder = new GsonBuilder(); 
builder.registerTypeAdapter(LoginRequest.class, new LoginRequestCreator()); 
Gson parser = builder.create(); 
Request request = parser.fromJson(completeInput, LoginRequest.class); 
System.out.println(request); 

und ich bekomme die erwartete Ausgabe.

Das, was ich tun möchte, ist die Linie Request request = parser.fromJson(completeInput, LoginRequest.class); mit etwas Ähnliches wie Request request = parser.fromJson(completeInput, Request.class); zu ersetzen, sondern tun, das wird nicht funktionieren, da Request eine Schnittstelle ist.

Ich möchte meine Gson den entsprechenden Typ der Anfrage abhängig von der empfangenen JSON zurückgeben.

{ 
    "type":"LOGIN", 
    "username":"someuser", 
    "password":"somepass" 
} 

Um es zu wiederholen, für eine Art und Weise Anfragen zu analysieren (In JSON) Umsetzung Suche von Kunden und die Rück Objekte der Klassen der:

Ein Beispiel für den JSON ich an den Server weitergeleitet unten gezeigt Request Schnittstelle

+0

Können Sie bitte weitere Beispiele für die verschiedenen JSON-Antworten angeben, die Sie vom Server erhalten können? Denn wenn Sie nicht viele und sehr unterschiedliche Möglichkeiten haben, können Sie einfach etwas tun ... – MikO

+0

Danke @MiKO für Ihre Eingabe. Andere wahrscheinliche Anfragen sind "PlayRequest", "LogoutRequest", "GetPlayersRequest", "JoinGameRequest", "StartGameRequest" usw. – fredmanglis

+0

Ich meine, wenn Sie ein Beispiel für die JSON-Anfrage für mindestens einen dieser anderen Arten von Anfragen angeben können . Ich meine, für deine 'LoginRequest' hast du Felder:' type', 'username' und' password', was ist mit anderen Anfragen? Wie sehen sie aus? – MikO

Antwort

7

Unter der Annahme, dass die verschiedenen möglichen JSON-Anfragen Sie können sie nicht sehr verschieden sind, schlage ich einen anderen Ansatz, einfacher meiner Meinung nach.

Lassen Sie uns sagen, dass Sie diese 3 verschiedene JSON-Anfragen haben:

{ 
    "type":"LOGIN", 
    "username":"someuser", 
    "password":"somepass" 
} 
//////////////////////////////// 
{ 
    "type":"SOMEREQUEST", 
    "param1":"someValue", 
    "param2":"someValue" 
} 
//////////////////////////////// 
{ 
    "type":"OTHERREQUEST", 
    "param3":"someValue" 
} 

Gson ermöglicht es Ihnen, eine einzige Klasse zu Wrap haben alle möglichen Antworten, wie folgt aus:

public class Request { 
    @SerializedName("type") 
    private String type; 
    @SerializedName("username") 
    private String username; 
    @SerializedName("password") 
    private String password; 
    @SerializedName("param1") 
    private String param1; 
    @SerializedName("param2") 
    private String param2; 
    @SerializedName("param3") 
    private String param3; 
    //getters & setters 
} 

von Mit der Annotation @SerializedName, wenn Gson versucht, die JSON-Anfrage zu analysieren, sieht es für jedes benannte Attribut in der Klasse aus, ob es in der JSON-Anfrage ein Feld mit dem gleichen Namen gibt. Wenn es kein solches Feld gibt, wird das Attribut in der Klasse nur auf null festgelegt.

Auf diese Weise können nur viele verschiedene JSON Antworten mit parsen können Ihre Request Klasse, wie folgt aus:

Gson gson = new Gson(); 
Request request = gson.fromJson(jsonString, Request.class); 

Sobald Sie Ihre JSON-Anforderung in Ihrer Klasse analysiert haben, können Sie die Daten aus der Wrap übertragen Klasse zu einem konkreten XxxxRequest Objekt, so etwas wie:

switch (request.getType()) { 
    case "LOGIN": 
    LoginRequest req = new LoginRequest(request.getUsername(), request.getPassword()); 
    break; 
    case "SOMEREQUEST": 
    SomeRequest req = new SomeRequest(request.getParam1(), request.getParam2()); 
    break; 
    case "OTHERREQUEST": 
    OtherRequest req = new OtherRequest(request.getParam3()); 
    break; 
} 

Beachten Sie, dass dieser Ansatz ein bisschen langweilig wird, wenn Sie viele verschiedene JSON-Anfragen und jene requ haben Die Unterschiede sind sehr unterschiedlich, aber trotzdem denke ich, dass es ein guter und sehr einfacher Ansatz ist ...

+0

Danke @MikO. Ich nehme an, dann könnte die 'switch-case' Struktur in irgendeine Anfrage-Fabrik von Art gehen. Vielen Dank. Das war hilfreich. Lass mich das untersuchen. – fredmanglis

+0

Ja, den Switch in eine 'RequestFactory'-Klasse zu setzen, macht definitiv Sinn. – MikO

0

Standardmäßig kann GSON keine als JSON serialisierten Klassen unterscheiden. Mit anderen Worten, Sie müssen dem Parser explizit mitteilen, welche Klasse Sie erwarten.

könnte eine Lösung sein benutzerdefinierte oder unter Verwendung eines Typ Adapter Deserialisieren, wie here beschrieben.

23

Polymorphes Mapping des beschriebenen Typs ist in Gson nicht verfügbar, ohne dass ein gewisses Maß an benutzerdefinierter Codierung erforderlich ist. Es gibt einen Erweiterungstypadapter, der as an extra verfügbar ist, der einen Großteil der Funktionalität bietet, nach der Sie suchen, mit dem Vorbehalt, dass die polymorphen Untertypen vorzeitig in dem Adapter deklariert werden müssen. Hier ist ein Beispiel für seine Verwendung:

public interface Response {} 

public interface Request { 
    public Response process(); 
} 

public class LoginRequest implements Request { 
    private String userName; 
    private String password; 

    // Constructors, getters/setters, overrides 
} 

public class PingRequest implements Request { 
    private String host; 
    private Integer attempts; 

    // Constructors, getters/setters, overrides 
} 

public class RequestTest { 

    @Test 
    public void testPolymorphicSerializeDeserializeWithGSON() throws Exception { 
     final TypeToken<List<Request>> requestListTypeToken = new TypeToken<List<Request>>() { 
     }; 

     final RuntimeTypeAdapterFactory<Request> typeFactory = RuntimeTypeAdapterFactory 
       .of(Request.class, "type") 
       .registerSubtype(LoginRequest.class) 
       .registerSubtype(PingRequest.class); 

     final Gson gson = new GsonBuilder().registerTypeAdapterFactory(
       typeFactory).create(); 

     final List<Request> requestList = Arrays.asList(new LoginRequest(
       "bob.villa", "passw0rd"), new LoginRequest("nantucket.jones", 
       "crabdip"), new PingRequest("example.com", 5)); 

     final String serialized = gson.toJson(requestList, 
       requestListTypeToken.getType()); 
     System.out.println("Original List: " + requestList); 
     System.out.println("Serialized JSON: " + serialized); 

     final List<Request> deserializedRequestList = gson.fromJson(serialized, 
       requestListTypeToken.getType()); 

     System.out.println("Deserialized list: " + deserializedRequestList); 
    } 
} 

Beachten Sie, dass Sie tatsächlich brauchen nicht die type Eigenschaft auf den einzelnen Java-Objekte zu definieren - es existiert nur in der JSON.

+3

Für diejenigen, die 'RuntimeTypeAdapterFactory' vermissen, können Sie dieses [gson-extras] (https://github.com/DanySK/gson-extras) verwenden, das auf maven-central verfügbar ist (der Zweck des Projekts besteht nur darin, es zu erstellen) verfügbar auf maven-central). – Tomask

4

Genson Bibliothek bietet standardmäßig Unterstützung für polymorphe Typen. Hier ist, wie es funktionieren würde:

// tell genson to enable polymorphic types support 
Genson genson = new Genson.Builder().setWithClassMetadata(true).create(); 

// json value will be {"@class":"mypackage.LoginRequest", ... other properties ...} 
String json = genson.serialize(someRequest); 
// the value of @class property will be used to detect that the concrete type is LoginRequest 
Request request = genson.deserialize(json, Request.class); 

Sie können auch Aliase für Ihre Typen verwenden.

// a better way to achieve the same thing would be to use an alias 
// no need to use setWithClassMetadata(true) as when you add an alias Genson 
// will automatically enable the class metadata mechanism 
genson = new Genson.Builder().addAlias("loginRequest", LoginRequest.class).create(); 

// output is {"@class":"loginRequest", ... other properties ...} 
genson.serialize(someRequest);