java 将 Gson 与接口类型一起使用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16396904/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 22:51:21  来源:igfitidea点击:

Using Gson with Interface Types

javajsongson

提问by fredmanglis

I am working on some server code, where the client sends requests in form of JSON. My problem is, there are a number of possible requests, all varying in small implementation details. I therefore thought to use a Request interface, defined as:

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

public interface Request {
    Response process ( );
}

From there, I implemented the interface in a class named LoginRequestas shown:

从那里,我在一个名为的类中实现了接口,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 + "]";
    }
}

To work with JSON, I created a GsonBuilderinstance and registered an InstanceCreatoras shown:

为了使用 JSON,我创建了一个GsonBuilder实例并注册了一个InstanceCreator,如下所示:

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

which I then used as shown in the snippet below:

然后我使用它,如下面的片段所示:

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

and I get the expected output.

我得到了预期的输出。

The thing I wish to do is replace the line Request request = parser.fromJson(completeInput, LoginRequest.class);with something similar to Request request = parser.fromJson(completeInput, Request.class);but doing that will not work, since Requestis an interface.

我想做的事情是用Request request = parser.fromJson(completeInput, LoginRequest.class);类似的东西替换该行,Request request = parser.fromJson(completeInput, Request.class);但这样做将不起作用,因为它Request是一个接口。

I want my Gsonto return the appropriate type of request depending on the received JSON.

我希望Gson根据收到的 JSON 返回适当类型的请求。

An example of the JSON I passed to the server is shown below:

我传递给服务器的 JSON 示例如下所示:

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

To reiterate, I am looking for a way to parse requests (In JSON) from clients and return objects of classes implementing the Requestinterface

重申一下,我正在寻找一种方法来解析来自客户端的请求(在 JSON 中)并返回实现该Request接口的类的对象

采纳答案by MikO

Assuming that the different possible JSON requests you may have are not extremely different to each other, I suggest a different approach, simpler in my opinion.

假设您可能拥有的不同的可能 JSON 请求彼此之间并没有太大的不同,我建议采用不同的方法,在我看来更简单。

Let's say that you have these 3 different JSON requests:

假设您有这 3 个不同的 JSON 请求:

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

Gson allows you to have a single class to wrapall the possible responses, like this:

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
}

By using the annotation @SerializedName, when Gson try to parse the JSON request, it just look, for each named attribute in the class, if there's a field in the JSON request with the same name. If there's no such field, the attribute in the class is just set to null.

通过使用注解@SerializedName,当 Gson 尝试解析 JSON 请求时,它只是查看类中的每个命名属性,是否在 JSON 请求中存在同名的字段。如果没有这样的字段,类中的属性就设置为null.

This way you can parse many different JSON responses using only your Requestclass, like this:

通过这种方式,您可以仅使用您的Request类来解析许多不同的 JSON 响应,如下所示:

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

Once you have your JSON request parsed into your class, you can transfer the data from the wrapclass to a concrete XxxxRequestobject, something like:

一旦您将 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;
}

Note that this approach gets a bit more tedious if you have many different JSON requests and those requests are very different to each other, but even so I think is a good and very simple approach...

请注意,如果您有许多不同的 JSON 请求并且这些请求彼此非常不同,则这种方法会变得有点乏味,但即便如此,我认为这是一种很好且非常简单的方法......

回答by Perception

Polymorphic mapping of the type described is not available in Gson without some level of custom coding. There is an extension type adapter available as an extrathat provides a bulk of the functionality you are looking for, with the caveat that the polymorphic sub-types need to be declared to the adapter ahead of time. Here is an example of its use:

如果没有某种级别的自定义编码,则所描述类型的多态映射在 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);
    }
}

Note that you don't actually need to define the typeproperty on the individual Java objects - it exists only in the JSON.

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

回答by eugen

Gensonlibrary provides support for polymorphic types by default. Here is how it would work:

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

You can also use aliases for your types.

您还可以为您的类型使用别名。

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

回答by Tony the Pony

By default, GSON cannot differentiate classes serialized as JSON; in other words, you will need to explicitly tell the parser what class you are expecting.

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

A solution could be custom deserializingor using a type adapter, as described here.

一个解决办法是定制反序列化或使用类型的适配器,如所描述这里

回答by Hugo Lewenhaupt

I found this answer: https://stackoverflow.com/a/28830173which solved my issue when using Calendar as the interface as the RunTimeType would be GregorianCalendar.

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

回答by 2sb

Have a utility method to create GSON for an interface of generic type.

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

// Utility method to register interface and its implementation to work with 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;
}

// Build Gson

// 构建 Gson

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

Use this gson to serialize or deserialize and this works.

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