it-swarm.com.de

Verwenden von Gson mit Schnittstellentypen

Ich arbeite an einem Servercode, bei dem der Client Anfragen in Form von JSON sendet. Mein Problem ist, dass es eine Anzahl möglicher Anforderungen gibt, die alle in kleinen Implementierungsdetails variieren. Daher dachte ich, eine Request-Schnittstelle zu verwenden, die wie folgt definiert ist:

public interface Request {
    Response process ( );
}

Von dort aus habe ich das Interface in einer Klasse namens LoginRequestimplementiert, wie gezeigt:

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

Um mit JSON zu arbeiten, habe ich eine GsonBuildername__-Instanz erstellt und eine InstanceCreatorwie folgt registriert:

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

die ich dann wie im nachstehenden Snippet verwendet habe:

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

und ich bekomme die erwartete Ausgabe.

Ich möchte die Zeile Request request = parser.fromJson(completeInput, LoginRequest.class); durch etwas ähnliches ersetzen, das Request request = parser.fromJson(completeInput, Request.class); ähnlich ist, aber dies wird nicht funktionieren, da Requesteine Schnittstelle ist.

Ich möchte, dass Gsonabhängig von der empfangenen JSON den entsprechenden Anforderungstyp zurückgibt.

Ein Beispiel für die JSON, die ich an den Server übergeben habe, ist unten dargestellt:

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

Um es noch einmal zu wiederholen, suche ich nach einer Möglichkeit, Anforderungen (In JSON) von Clients zu analysieren und Objekte von Klassen zurückzugeben, die das Interface Requestimplementieren

23
fredmanglis

Unter der Annahme, dass die verschiedenen möglichen JSON-Anforderungen, die Sie möglicherweise haben, sich nicht extrem voneinander unterscheiden, schlage ich einen anderen Ansatz vor, der meiner Meinung nach einfacher ist.

Nehmen wir an, Sie haben diese drei verschiedenen JSON-Anforderungen:

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

Mit Gson können Sie eine einzige Klasse für wrap alle möglichen Antworten wie die folgende verwenden:

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
}

Wenn Sie mit der Annotation @SerializedName versuchen, die JSON-Anforderung zu analysieren, wird nach jedem benannten Attribut in der Klasse gesucht, ob in der JSON-Anforderung ein Feld mit demselben Namen vorhanden ist. Wenn es kein solches Feld gibt, wird das Attribut in der Klasse einfach auf null gesetzt. 

Auf diese Weise können Sie viele verschiedene JSON-Antworten nur mit Ihrer Request-Klasse analysieren:

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

Sobald Sie Ihre JSON-Anforderung in Ihre Klasse geparst haben, können Sie die Daten von der Klasse wrap in ein konkretes XxxxRequest-Objekt übertragen.

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

Beachten Sie, dass dieser Ansatz etwas langweiliger wird, wenn Sie viele verschiedene JSON-Anforderungen haben und diese Anforderungen sehr unterschiedlich sind. Trotzdem denke ich, dass dies ein guter und sehr einfacher Ansatz ist ...

9
MikO

Eine polymorphe Abbildung des beschriebenen Typs ist in Gson nicht verfügbar, wenn Sie nicht über eine benutzerdefinierte Codierung verfügen. Es ist ein Erweiterungsadapter verfügbar - als Extra , der einen Großteil der gewünschten Funktionalität bereitstellt, mit der Einschränkung, dass die polymorphen Untertypen dem Adapter vorab deklariert werden müssen. Hier ist ein Beispiel für seine Verwendung:

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

Beachten Sie, dass Sie die type-Eigenschaft für die einzelnen Java-Objekte nicht wirklich definieren müssen. Sie ist nur in JSON vorhanden.

26
Perception

Genson library unterstützt standardmäßig polymorphe Typen. So würde es funktionieren:

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

Sie können auch Aliase für Ihre Typen verwenden.

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

Standardmäßig kann GSON nicht als JSON serialisierte Klassen unterscheiden. Mit anderen Worten, Sie müssen dem Parser explizit mitteilen, welche Klasse Sie erwarten.

Eine Lösung könnte sein: custom deserializing oder Verwendung eines type Adapters , wie hier hier beschrieben.

0
Tony the Pony