28

我正在处理一些服务器代码,客户端以 JSON 的形式发送请求。我的问题是,有许多可能的请求,都在小的实现细节上有所不同。因此,我想使用一个 Request 接口,定义为:

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接口的类的对象

4

6 回答 6

28

如果没有一定程度的自定义编码,Gson 中不提供所描述类型的多态映射。有一个扩展类型适配器作为额外提供,它提供了您正在寻找的大量功能,但需要提前向适配器声明多态子类型。以下是它的使用示例:

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

请注意,您实际上不需要type在单个 Java 对象上定义属性 - 它仅存在于 JSON 中。

于 2013-05-06T14:30:49.660 回答
9

假设您可能拥有的不同可能的 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 请求并且这些请求彼此非常不同,那么这种方法会变得有点乏味,但即便如此,我认为这是一种很好且非常简单的方法......

于 2013-05-06T16:59:20.537 回答
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);
于 2013-05-06T15:29:09.483 回答
0

默认情况下,GSON 无法区分序列化为 JSON 的类;换句话说,您需要明确地告诉解析器您期望什么类。

解决方案可以是自定义反序列化或使用类型适配器,如此所述。

于 2013-05-06T11:11:05.263 回答
0

我找到了这个答案:https : //stackoverflow.com/a/28830173 它解决了我在使用日历作为接口时的问题,因为 RunTimeType 将是 GregorianCalendar。

于 2020-05-13T09:34:38.810 回答
0

有一个实用方法来为泛型类型的接口创建 GSON。

// 注册接口及其实现以使用 GSON 的实用方法

public static <T> Gson buildInterface(Class<T> interfaceType, List<Class<? extends T>> interfaceImplmentations) {
    final RuntimeTypeAdapterFactory<T> typeFactory = RuntimeTypeAdapterFactory.of(interfaceType, "type");
    for (Class<? extends T> implementation : interfaceImplmentations) {
        typeFactory.registerSubtype(implementation);
    }
    final Gson gson = new GsonBuilder().registerTypeAdapterFactory(typeFactory).create();
    return gson;
}

// 构建 Gson

List<Class<? extends Request>> customConfigs = new ArrayList<>();
customConfigs.add(LoginRequest.getClass());
customConfigs.add(SomeOtherRequest.getClass());
Gson gson = buildInterface(Request.class, customConfigs);

使用这个 gson 来序列化或反序列化,这很有效。

于 2020-05-26T23:24:29.797 回答