2016-11-07 3 views
0

Ich versuche, eine Methode in einer Dropwizard-Ressource zu implementieren, die einen Aufruf von einem JS-Frontend (das DataTables verwendet) bedient.Deserializing-Liste <Map <String, String >> QueryParam in Jersey 1

Die Anfrage hat Abfrageparameter, die wie folgt aussehen:

Spalten [0] [Daten] = 0 & Spalten [0] [name] = & Spalten [0] [durchsuchbare] = false & Spalten [0] [bestellbaren] = false & Spalten [0] [Suche] [Wert] = & Spalten [0] [Suche] [regex] = false

Spalten [1] [Daten] = iata & Spalten [1 ] [Name] = iata & Spalten [1] [durchsuchbar] = wahr & Spalten [1] [bestellbar] = wahr & Spalten [1] [Suche] [Wert] = & Spalten [1] [Suche] [regex] = false

Die Anforderung von einem JS-Frontend mit Datentabellen implementiert kommt, und verwendet serverseitige Verarbeitung. Info darüber, wie Datentabellen sendet die Anforderungen hier:

https://datatables.net/manual/server-side

Ich habe Probleme mit dem Datentyp für die oben genannten Abfrageparameter definieren. Mit Federdaten, können wir es als definieren:

List<Map<String, String>> columns 

, die in einem Objekt gewickelt werden können mit ModelAttribute kommentiert und es wird deserialisieren in Ordnung.

In meiner App verwende ich eine ältere Version von Dropwizard, die auf Jersey 1.19 abhängt. Ich habe versucht, es als ein QueryParam kommentieren, aber die App schlägt beim Start fehl.

Methode:

@Path("/mappings") 
@GET 
@Timed 
@Consumes(MediaType.APPLICATION_JSON) 
@Produces(MediaType.APPLICATION_JSON) 
public Response getMappings(@QueryParam("columns") List<Map<String, String>> columns) { 
    // processing here. 
} 

Wenn ich das tue, erhalte ich:

ERROR [2016.11.07 14: 16: 13.061] com.sun.jersey.spi.inject. Fehler: Die folgenden Fehler und Warnungen wurden mit Ressourcen und/oder Anbieterklassen erkannt: SCHWERE: Abhängigkeits für Methode Fehlende öffentlichen javax.ws.rs.core.Response com.ean.gds.proxy.ams.application. resource.gui.IataMappingGuiResource.getMappings (java.util.List) bei Parametern bei Index 0 WARN [2016.11.07 14: 16: 13.070] /: nicht verfügbar

Meiner Frage ist: Ich habe ich keine andere Wahl, als für sie einen benutzerdefinierten Deserializer schreiben?

Hinweis: Wenn ich die Anfrage mit @Context ergreife, kann ich sehen, dass die decodedQueryParams eine MultivaledMap sind, die String-Schlüssel wie "columns [0] [data]" Listen von String-Werten zuordnet, die immer einen einzigen haben Element, das ist der Wert.

Update: Nach einigem Graben fand ich die folgende JAX-RS-Spezifikation (Abschnitt 3.2), die erklärt, warum mein Ansatz nicht gültig beginnen:

Folgende Typen werden unterstützt:

  1. Urtyp

  2. Typen, die einen Konstruktor haben, die eine einzelne akzeptiert String-Argument

  3. Typen, die eine statische Methode namens valueOf mit einem einzelnen String-Argument haben.

  4. Liste, Set oder SortedSet wobei T 2 oder 3 erfüllt.

Quelle: Handling Multiple Query Parameters in Jersey

Also habe ich versucht, statt nur eine Liste mit. Dies stürzt die App beim Start nicht ab, aber wenn die Anfrage eingeht, wird sie in eine leere Liste deserialisiert. Es bleibt also die Frage, welcher Ansatz richtig ist.

+0

Dies ist etwas, das Sie manuell analysieren müssen. Oder finde eine Bibliothek, die sie zu analysieren weiß. Jersey ist nicht schlau genug für diese –

+0

Erlaubt Ihnen diese Client-Bibliothek nicht, die Daten im JSON-Format zu senden? Wenn Sie bei Abfrageparametern bleiben müssen, anstatt sie im Hauptteil zu senden, können Sie das JSON immer noch viel einfacher parsen, als Sie das aktuelle Format verwenden könnten. Ich weiß, dass die meisten JS Datat Bibliotheken erlauben JSON-Format –

+0

@peeskillet Leider tut es nicht. Ich habe keine Kontrolle darüber, wie die Bibliothek diese Daten in der Anfrage sendet. Ich gehe derzeit nur für einen benutzerdefinierten Parser. Vielen Dank ! –

Antwort

1

In der Tat verwenden Sie eine so sehr unterschiedliche Struktur von allen gängigen, die wir für Rest Web Services abgebildet haben. Aufgrund dieses strukturellen Kompatibilitätsproblems wird der Versuch, JSON zum Marshalm/Unmarshaling der Werte zu verwenden, nicht geeignet sein, sobald keine objektbasierten Parameter transportiert werden.

Aber wir haben ein paar Optionen, um diese Situation zu umgehen. Mal sehen:

  1. Möchten Sie die @QueryParam Strategie nicht möglich ist, weil aus zwei Hauptgründe:

    • Wie Sie bemerkt haben, gibt es einige Einschränkungen auf seiner Verwendung in Bezug auf Collections andere als Lists, Sets, etc ;
    • Diese Annotation bildet eine (oder eine Liste) von Parametern durch ihre (ihre) Namen ab, so dass Sie jeden einzelnen Parameter (getrennt durch &) benötigen, um denselben Namen zu haben. Es ist einfacher, wenn wir über ein Formular nachdenken, das (über GET) eine Liste mit Checkbox-Werten übermittelt: Sobald alle dieselbe Eigenschaft name haben, werden sie im Format "name=value1&name=value2" gesendet.

    Also, um diese Anforderung zu erhalten, würden Sie machen so etwas wie:

    @GET 
    public Response getMappings(@QueryParam("columns") List<String> columns) { 
        return Response.status(200).entity(columns).build(); 
    } 
    
    // URL to be called (with same param names): 
    // /mappings?columns=columns[1][name]=0&columns=columns[0][searchable]=false 
    
    // Result: [columns[1][name]=0, columns[0][searchable]=false] 
    

    Sie auch eine benutzerdefinierten Java-Typ zu schaffen für Param Anmerkungen versuchen können, wie Sie see here. Das würde Codierungsfehler vermeiden, aber in meinen Tests funktionierte es nicht für das Bracket-Problem. :(

  2. Sie können regex verwenden zusammen mit @Path Anmerkung zu definieren, was von einem String Parameter akzeptiert werden wird. Leider würde Ihre URL von unvalid characteres zusammengesetzt sein (wie die Klammern []), was bedeutet, Ihre Server gehen wird um eine 500 error zurückgeben

    Eine Alternative dafür ist, wenn Sie diese Zeichen für gültige "ersetzen" (wie Unterstreichungszeichen, z):

    @GET 
    @Path("/{columns: .*}") 
    public Response getMappings(@PathParam("columns") String columns) { 
        return Response.status(200).entity(columns).build(); 
    } 
    
    // Result: columns_1_=0&columns_1__name_= 
    
  3. Eine viel bessere Möglichkeit, dies zu tun ist durch UriInfo Objekt, wie Sie versucht haben:

    /mappings/columns_1_=0&columns_1__name_= 
    

    Auf diese Weise kann die Lösung ohne Sorgen angewendet werden. Dies ist einfacher, da die URL und die Parameter nicht geändert werden müssen. Das Objekt hat eine getQueryParameters(), die eine Map mit den param Werte zurückgibt:

    @GET 
    public Response getMappings(@Context UriInfo uriInfo) { 
        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters(); 
    
        // In case you want to get the whole generated string 
        String query = uriInfo.getRequestUri().getQuery(); 
    
        String output = "QueryParams: " + queryParams 
          + "<br> Keys: " + queryParams.keySet() 
          + "<br> Values: " + queryParams.values() 
          + "<br> Query: " + query; 
    
        return Response.status(200).entity(output).build(); 
    } 
    
    // URL: /mappings?columns[1][name]=0&columns[0][searchable]=false 
    
    /* Result: 
    * QueryParams: {columns[0][searchable]=[false], columns[1][name]=[0]} 
    * Keys: [columns[0][searchable], columns[1][name]] 
    * Values: [[false], [0]] 
    * Query: columns[1][name]=0&columns[0][searchable]=false 
    */ 
    

    Allerdings muss man sich bewusst sein, dass, wenn Sie diesen Ansatz folgen (unter Verwendung eines Map) Sie können keine Schlüssel dupliziert haben, sobald die Struktur doesn unterstütze es nicht. Deshalb habe ich die getQuery() Option, wo Sie die ganze Zeichenfolge erhalten.

  4. Eine letzte Möglichkeit ist die Erstellung einer InjectableProvider, aber ich kann nicht viele Diffs zu der getQuery() Strategie sehen (da Sie es teilen und Ihre eigene Karte von Werten erstellen können).
+0

Ja, die dritte Option ist, was ich am Ende benutzt habe. Es gibt mir die größte Flexibilität (wenn auch nicht viel) in Bezug auf das Manövrieren der Daten. Ich werde diese Antwort als akzeptiert markieren. Vielen Dank ! –

+0

Gern geschehen! :) – bosco

Verwandte Themen