反序列化List <map <string,string =“”>&gt; QueryParam in jersey 1

时间:2016-11-07 14:22:40

标签: java datatables jersey dropwizard

我试图在dropwizard资源中实现一个方法,该方法将服务来自JS前端(使用DataTables)的调用。

请求的查询参数如下所示:

  

列[0] [数据] = 0&安培;列[0] [名称] =&安培;列[0] [搜索] = FALSE&安培;列[0] [订购] = FALSE&安培;列[0] [搜索] [值] =&安培;列[0] [搜索] [正则表达式] = FALSE

     

列[1] [数据] = IATA&安培;列[1] [名称] = IATA&安培;列[1] [搜索] =真&安培;列[1] [订购] =真&安培;列[1] [搜索] [值] =&安培;列[1] [搜索] [正则表达式] = FALSE

请求来自使用DataTables实现的JS前端,并使用服务器端处理。有关数据表如何在此处发送请求的信息:

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

我在定义上述查询参数的数据类型时遇到问题。使用spring数据,我们可以将其定义为:

List<Map<String, String>> columns

可以包装在用ModelAttribute注释的对象中,它会反序列化。

在我的应用程序中,我使用的旧版本的dropwizard依赖于jersey 1.19。 我尝试将其注释为QueryParam,但应用程序在启动时失败。

方法:

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

当我这样做时,我得到:

  

ERROR [2016-11-07 14:16:13,061] com.sun.jersey.spi.inject.Errors:The   已使用资源和/或检测到以下错误和警告   提供程序类:SEVERE:缺少方法public的依赖项   javax.ws.rs.core.Response   com.ean.gds.proxy.ams.application.resource.gui.IataMappingGuiResource.getMappings(java.util.List中)   at index at index 0 WARN [2016-11-07 14:16:13,070] /:不可用

我的问题是:除了为它编写自定义反序列化器之外,我还有其他选择吗?

注意:如果我使用@Context获取请求,我可以看到decodeQueryParams是一个多值映射,它映射字符串键,如&#34; columns [0] [data]&#34; to String列表,它总是有一个元素,即值。

更新 经过一番挖掘后,我发现了以下JAX-RS规范(第3.2节),该规范解释了为什么我的方法开始时无效:

  

支持以下类型:

     
      
  1. 原始类型

  2.   
  3. 具有接受单个String参数的构造函数的类型。

  4.   
  5. 具有名为valueOf且带有单个String参数的静态方法的类型。

  6.   
  7. List,Set或SortedSet,其中T满足2或3以上。

  8.   

来源:Handling Multiple Query Parameters in Jersey

所以我尝试过只使用List。这不会在启动时使应用程序崩溃,但是当请求进入时,它会反序列化为空列表。所以问题仍然是什么方法是正确的。

1 个答案:

答案 0 :(得分:3)

事实上,您使用的是与我们为Rest Web Services完善映射的所有常见结构完全不同的结构。此外,由于这种结构合规性问题,一旦我们没有传输基于对象的参数,尝试使用JSON来编组/解组值将不适合。

但是,我们有几个选择“解决这种情况”。我们来看看:

  1. 由于两个主要原因,无法采用@QueryParam策略:

    • 正如您所注意到的,Collections除了ListsSets等之外,对其使用有一些限制;
    • 此注释按其(的)名称映射一个(或一个列表)param(s),因此您需要每个参数(由&分隔)具有相同的名称。当我们考虑提交(通过GET)复选框值列表的表单时会更容易:一旦它们都具有相同的name属性,它们将以"name=value1&name=value2"格式发送。

    因此,为了达到这个要求,你必须做出类似的事情:

    @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]
    

    您也可以尝试为see here创建 Param Annotations的自定义Java类型。这样可以避免编码问题,但在我的测试中,它不适用于括号问题。 :(

  2. 您可以使用正则表达式和@Path注释来定义String参数将接受的内容。不幸的是,您的网址将由无效的字符组成(如方括号[]),这意味着您的服务器将返回500 error

    另一种替代方法是,如果你将这个字符“替换”为有效字符(例如下划线字符,例如):

    /mappings/columns_1_=0&columns_1__name_=
    

    这样,解决方案可以毫无后顾之忧地应用:

    @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. 更好的方法是通过UriInfo对象,你可能已经尝试过了。这更简单,因为不需要更改URL和params。该对象具有getQueryParameters(),其返回带有参数值的Map

    @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
     */
    

    但是,您必须注意,如果您遵循此方法(使用Map),一旦结构不支持,您就不能拥有重复的密钥。这就是为什么我在getQuery()选项中包含整个字符串的原因。

  4. 最后一种可能是创建InjectableProvider,但我看不到getQuery()策略的许多差异(因为你可以拆分它并创建自己的值映射)。