2013-05-06 38 views
18

Tôi đang làm việc trên một số mã máy chủ, trong đó máy khách gửi yêu cầu dưới dạng JSON. Vấn đề của tôi là, có một số yêu cầu có thể, tất cả thay đổi trong các chi tiết triển khai nhỏ. tôi do đó nghĩ để sử dụng một giao diện yêu cầu, định nghĩa là:Sử dụng Gson với các loại giao diện

public interface Request { 
    Response process (); 
} 

Từ đó, tôi thực hiện giao diện trong một lớp có tên LoginRequest như:

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

Để làm việc với JSON, tôi đã tạo ra một GsonBuilder dụ và đăng ký một InstanceCreator như:

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

mà tôi sau đó sử dụng như trong đoạn mã bên dưới:

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

và tôi nhận được kết quả mong đợi.

Điều tôi muốn làm là thay thế dòng Request request = parser.fromJson(completeInput, LoginRequest.class); bằng thứ gì đó tương tự như Request request = parser.fromJson(completeInput, Request.class); nhưng thực hiện điều đó sẽ không hoạt động, vì Request là giao diện.

Tôi muốn Gson trả lại loại yêu cầu thích hợp tùy thuộc vào JSON đã nhận.

Một ví dụ về JSON tôi truyền cho các máy chủ được hiển thị dưới đây:

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

Nói cách khác, tôi đang tìm kiếm một cách để phân tích yêu cầu (Trong JSON) từ khách hàng và trở về đối tượng của các lớp thực hiện Request Giao diện

+0

Bạn có thể vui lòng cung cấp các ví dụ khác về các câu trả lời JSON khác nhau mà bạn có thể nhận được từ máy chủ? Bởi vì nếu bạn không có nhiều khả năng và rất khác nhau, có điều bạn có thể làm dễ dàng ... – MikO

+0

Cảm ơn @MiKO cho thông tin bạn nhập. Các yêu cầu có khả năng khác là 'PlayRequest',' LogoutRequest', 'GetPlayersRequest',' JoinGameRequest', 'StartGameRequest' etc ... – fredmanglis

+0

Ý tôi là nếu bạn có thể cung cấp ví dụ về yêu cầu JSON cho ít nhất một trong các loại yêu cầu khác . Ý tôi là, đối với 'LoginRequest' của bạn, bạn có fiels:' type', 'username' và' password', còn các yêu cầu khác thì sao? Họ trông như thế nào? – MikO

Trả lời

7

Giả sử rằng các yêu cầu JSON có thể khác nhau mà bạn có thể có không khác biệt với nhau, tôi đề xuất một cách tiếp cận khác, đơn giản hơn theo ý kiến ​​của tôi.

Hãy nói rằng bạn có những 3 yêu cầu JSON khác nhau:

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

Gson cho phép bạn có một lớp duy nhất để bọc tất cả các câu trả lời có thể, như thế này:

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 
} 

Bằng bằng cách sử dụng chú thích @SerializedName, khi Gson cố gắng phân tích cú pháp yêu cầu JSON, nó chỉ xem xét, cho mỗi thuộc tính được đặt tên trong lớp, nếu có một trường trong yêu cầu JSON có cùng tên. Nếu không có trường như vậy, thuộc tính trong lớp chỉ được đặt thành null.

Bằng cách này bạn có thể phân tích nhiều phản hồi JSON khác nhau chỉ sử dụng lớp Request của bạn, như thế này:

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

Một khi bạn đã yêu cầu JSON của bạn phân tách thành lớp học của bạn, bạn có thể chuyển dữ liệu từ bọc lớp cho một đối tượng cụ thể XxxxRequest, một cái gì đó như:

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

Lưu ý rằng phương pháp này được một chút tẻ nhạt hơn nếu bạn có nhiều yêu cầu khác nhau JSON và những requ ests là rất khác nhau với nhau, nhưng ngay cả như vậy tôi nghĩ là một cách tiếp cận tốt và rất đơn giản ...

+0

Cảm ơn @MikO. Tôi đoán cấu trúc 'switch-case' có thể đi vào một số nhà máy Request. Cảm ơn. Điều đó rất hữu ích. Hãy để tôi nhìn vào đó. – fredmanglis

+0

Có, việc chuyển đổi thành lớp 'RequestFactory' chắc chắn có ý nghĩa. – MikO

0

Theo mặc định, GSON không thể phân biệt các lớp được tuần tự hóa dưới dạng JSON; nói cách khác, bạn sẽ cần phải nói rõ với trình phân tích cú pháp lớp nào bạn đang mong đợi.

Một giải pháp có thể được tùy chỉnh deserializing hoặc sử dụng một bộ chuyển đổi loại, như mô tả here.

23

Ánh xạ đa hình của loại được mô tả không có sẵn trong Gson mà không có một số mức mã hóa tùy chỉnh. Có bộ điều hợp loại mở rộng có sẵn as an extra cung cấp một lượng lớn chức năng bạn đang tìm kiếm, với báo trước rằng các loại phụ đa hình cần được khai báo với bộ điều hợp trước thời hạn. Dưới đây là một ví dụ về việc sử dụng nó:

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

Lưu ý rằng bạn không thực sự cần phải xác định type tài sản trên các đối tượng Java cá nhân - nó chỉ tồn tại trong JSON.

+3

Đối với những người thiếu 'RuntimeTypeAdapterFactory', bạn có thể sử dụng [gson-extras] này (https://github.com/DanySK/gson-extras) có sẵn trên maven-central (mục đích của dự án là chỉ để làm cho nó có sẵn trên maven-central). – Tomask

4

Genson thư viện cung cấp hỗ trợ cho các loại đa hình theo mặc định. Dưới đây là cách hoạt động:

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

Bạn cũng có thể sử dụng bí danh cho loại của mình.

// 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); 
Các vấn đề liên quan