2013-05-06 7 views
18

Pracuję nad kodem serwera, w którym klient wysyła żądania w formie JSON. Mój problem polega na tym, że istnieje wiele możliwych żądań, z których wszystkie różnią się małymi szczegółami implementacji. Dlatego uważa się, że korzystać z interfejsu żądania, zdefiniowana jako:Używanie Gson z typami interfejsów

public interface Request { 
    Response process (); 
} 

Stamtąd I wdrożone interfejsu w klasie o nazwie LoginRequest jak pokazano:

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 + "]"; 
    } 
} 

Aby pracować z JSON, stworzyłem GsonBuilder przykład i zarejestrował InstanceCreator jak pokazano poniżej:

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

które następnie stosuje się, jak pokazano na opis poniżej:

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

i otrzymuję oczekiwany wynik.

Rzeczą, którą chcę zrobić, jest zastąpienie linii Request request = parser.fromJson(completeInput, LoginRequest.class); czymś podobnym do Request request = parser.fromJson(completeInput, Request.class);, ale to nie zadziała, ponieważ Request jest interfejsem.

Chcę, aby mój Gson zwrócił odpowiedni typ żądania w zależności od otrzymanego JSON.

Przykładem JSON I przekazywane do serwera znajduje się poniżej:

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

Powtórzmy, szukam sposób do analizowania żądań (w JSON) z klientami i powrotu obiektów klas wykonawczego Request interfejs

+0

Czy możesz podać inne przykłady różnych odpowiedzi JSON, które możesz uzyskać z serwera? Bo jeśli nie masz wielu i bardzo różnych możliwości, jest coś, co możesz zrobić łatwo ... – MikO

+0

Dzięki @MiKO za twoje wejście. Inne prawdopodobne żądania to 'PlayRequest',' LogoutRequest', 'GetPlayersRequest',' JoinGameRequest', 'StartGameRequest' itd ... – fredmanglis

+0

Mam na myśli, jeśli możesz podać przykład żądania JSON dla co najmniej jednego z tych innych typów żądań . Mam na myśli, dla twojego 'LoginRequest' masz pola:' type', 'username' i' password', a co z innymi żądaniami? Jak wyglądają? – MikO

Odpowiedz

7

Zakładając, że różne wnioski możliwe JSON Mogłeś nie są bardzo różnią się od siebie, proponuję inne podejście, prostszy w mojej opinii.

Załóżmy, że masz te 3 różne żądania JSON:

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

Gson pozwala mieć jedną klasę do owinąć wszystkie możliwe odpowiedzi, tak:

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 
} 

By przy użyciu adnotacji @SerializedName, gdy Gson próbuje przeanalizować żądanie JSON, po prostu sprawdza, dla każdego nazwanego atrybutu w klasie, czy istnieje pole w żądaniu JSON o tej samej nazwie. Jeśli nie ma takiego pola, atrybut w klasie jest ustawiony na null.

ten sposób można analizować różne odpowiedzi JSON tylko za pomocą klasy Request, tak:

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

Kiedy już Twoja prośba JSON analizowany w swojej klasie, można przesyłać dane z owinąć klasa do konkretnej XxxxRequest obiektu, coś jak:

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

Należy pamiętać, że takie podejście staje się trochę bardziej uciążliwe, jeśli masz wiele różnych żądań JSON i tych życzen esty są bardzo różne, ale mimo to uważam, że jest to dobre i bardzo proste podejście ...

+0

Dzięki @MikO. Domyślam się, że struktura 'switch-case' mogłaby trafić do jakiejś sortowni typu Request. Dzięki. To było pomocne. Pozwól mi spojrzeć w to. – fredmanglis

+0

Tak, umieszczenie przełącznika w klasie 'RequestFactory' ma zdecydowanie sens. – MikO

0

Domyślnie GSON nie może rozróżniać klas serializowanych jako JSON; innymi słowy, musisz wyraźnie powiedzieć analizatorowi, jakiej klasy się spodziewasz.

Rozwiązaniem może być zwyczaj deserializacji lub za pomocą adaptera typu jak opisano here.

23

Mapowanie polimorficzne opisanego typu nie jest dostępne w Gsonie bez pewnego poziomu niestandardowego kodowania. Dostępna jest karta rozszerzeń o numerze as an extra, która zapewnia większość funkcji, których szukasz, z zastrzeżeniem, że polimorficzne podtypy muszą być zadeklarowane do adaptera z wyprzedzeniem. Oto przykład użycia:

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

pamiętać, że w rzeczywistości nie trzeba zdefiniować właściwość type na poszczególne obiekty Java - to istnieje tylko w formacie JSON.

+3

Dla tych, którzy nie mają 'RuntimeTypeAdapterFactory', możesz użyć tego [gson-extras] (https://github.com/DanySK/gson-extras) dostępnego w witrynie maven-central (celem projektu jest tylko uczynienie go dostępny na maven-central). – Tomask

4

Genson Biblioteka domyślnie obsługuje typy polimorficzne. Oto, jak to będzie działać:

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

Możesz również użyć aliasów dla swoich typów.

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