2013-05-06 91 views
18

我正在處理一些服務器代碼,其中客戶端以JSON的形式發送請求。我的問題是,有很多可能的請求,所有請求都有所不同,這些請求在小的實現細節上有所不同 因此,我想用一個請求接口,定義爲:使用Gson與接口類型

public interface Request { 
    Response process (); 
} 

從那裏,我實現了在一個名爲LoginRequest類的界面如圖所示:

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

要使用JSON工作,我創建了一個GsonBuilder實例和註冊的InstanceCreator如圖所示:

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

我則如圖中所用下面的代碼片段:

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

,我得到預期的輸出。

我希望做的事情是替換線Request request = parser.fromJson(completeInput, LoginRequest.class);類似於Request request = parser.fromJson(completeInput, Request.class);的東西,但這樣做是行不通的,因爲Request是一個接口。

我希望我的Gson根據收到的JSON返回適當類型的請求。

我傳遞給服務器的JSON的一個例子如下所示:

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

要重申,我正在尋找一種方式來解析來自客戶端的請求(在JSON)並返回執行類的對象Request接口

+0

你能否提供其他的例子,你可以從服務器獲得不同的JSON響應?因爲如果你沒有很多不同的可能性,那麼你可以輕鬆地做... – MikO 2013-05-06 11:14:17

+0

感謝@MiKO的輸入。其他可能的請求是「PlayRequest」,「LogoutRequest」,「GetPlayersRequest」,「JoinGameRequest」,「StartGameRequest」等... – fredmanglis 2013-05-06 12:26:32

+0

我的意思是,如果您可以提供至少其中一種請求的JSON請求示例。我的意思是,對於你的'LoginRequest',你有類型:'type','username'和'password',那麼其他請求呢?他們看起來如何? – MikO 2013-05-06 12:49:51

回答

7

假設你可能有不同的可能JSON請求不是彼此非常不同,我建議採用不同的方法,在我看來簡單。

比方說,你有這3個不同的JSON請求:

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

GSON可以讓你有一個類來包裹所有可能的反應,就像這樣:

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 
} 

通過使用註釋@SerializedName,當Gson嘗試解析JSON請求時,對於該類中的每個指定屬性,如果JSON請求中有一個字段具有相同的名稱,則只需查看它。如果沒有這樣的字段,則該類中的屬性只設置爲null

這樣你可以只用你的Request類,像這樣的解析許多不同的JSON響應:

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

一旦你有你的JSON請求解析爲您的類,可以從包裝傳輸數據類具體XxxxRequest對象,像:

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

注意,這種方法得到位比較繁瑣,如果你有許多不同的JSON的請求和那些熱曲ests是彼此非常不同,但即使如此,我認爲是一個很好,很簡單的方法...

+0

謝謝@MikO。我想'switch-case'結構可以進入某種Request工廠。謝謝。這很有幫助。讓我看看。 – fredmanglis 2013-05-07 08:57:11

+0

是的,把開關放到一個'RequestFactory'類中肯定是有道理的。 – MikO 2013-05-07 09:12:26

0

默認情況下,GSON不能區分序列化爲JSON的類;換句話說,你需要明確地告訴解析器你期望的是什麼類。

的溶液可以是定製反序列化或使用型適配器,如所描述here

23

所描述類型的多態映射在Gson中沒有一定級別的自定義編碼是不可用的。有一個可用的擴展類型適配器as an extra,它提供了您正在尋找的大部分功能,但需要提前向適配器聲明多態子類型。下面是使用它的一個例子:

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

注意,你實際上並不需要定義在單個Java對象的type屬性 - 它只存在於JSON。

+3

對於那些缺少'RuntimeTypeAdapterFactory'的人,可以使用maven-central上可用的[gson-extras](https://github.com/DanySK/gson-extras)(該項目的目的只是爲了讓它在maven-central上可用)。 – Tomask 2017-05-30 09:17:48

4

Genson庫默認提供對多態類型的支持。這是它將如何工作:

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

您也可以使用別名爲您的類型。

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