【问题标题】:Deserializing List<Map<String, String>> QueryParam in jersey 1在球衣 1 中反序列化 List<Map<String, String>> QueryParam
【发布时间】:2016-11-07 14:22:39
【问题描述】:

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

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

columns[0][data]=0&columns[0][name]=&columns[0][searchable]=false&columns[0][orderable]=false&columns[0][search][value]=&columns[0] [搜索][正则表达式]=false

columns[1][data]=iata&columns[1][name]=iata&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1] [搜索][正则表达式]=false

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

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

我在为上述查询参数定义数据类型时遇到问题。有了spring data,我们可以定义为:

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

当我这样做时,我得到:

错误 [2016-11-07 14:16:13,061] com.sun.jersey.spi.inject.Errors: 使用资源和/或检测到以下错误和警告 提供者类:严重:缺少公共方法的依赖项 javax.ws.rs.core.Response com.ean.gds.proxy.ams.application.resource.gui.IataMappingGuiResource.getMappings(java.util.List) at 索引 0 处的参数 WARN [2016-11-07 14:16:13,070] /: 不可用

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

注意:如果我使用@Context 获取请求,我可以看到解码的QueryParams 是一个MultivaluedMap,它将诸如“columns[0][data]”之类的字符串键映射到字符串值列表,该列表始终具有单个元素,这就是价值。

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

支持以下类型:

  1. 原始类型

  2. 具有接受单个字符串参数的构造函数的类型。

  3. 具有名为 valueOf 的静态方法和单个 String 参数的类型。

  4. List、Set 或 SortedSet,其中 T 满足上述 2 或 3。

来源:Handling Multiple Query Parameters in Jersey

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

【问题讨论】:

  • 这是您必须手动解析的内容。或者找一个知道如何解析它的库。泽西在这方面不够聪明
  • 这个客户端库不允许您以 JSON 格式发送数据吗?如果您需要坚持查询参数而不是在正文中发送它,您仍然可以更轻松地解析 JSON,然后您就可以使用当前格式。我知道大多数 JS 数据表库都允许 JSON 格式
  • @peeskillet 不幸的是它没有。我无法控制图书馆如何在请求中发送这些数据。我目前正在使用自定义解析器。谢谢!

标签: java datatables jersey dropwizard


【解决方案1】:

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

但是,我们有几个选项可以“解决这种情况”。让我们看看:

  1. 采用@QueryParam 策略是不可能的,主要有两个原因:

    • 如您所见,除了ListsSets 等之外,Collections 的使用存在一些限制;
    • 此注释通过其名称映射一个(或一个列表)参数,因此您需要每个参数(由&amp; 分隔)具有相同的名称。当我们考虑一个提交(通过GET)复选框值列表的表单时会更容易:一旦它们都具有相同的name 属性,它们将以"name=value1&amp;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]
    

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

  2. 您可以使用正则表达式和@Path 注释来定义String 参数将接受的内容。不幸的是,您的 URL 将由无效字符组成(如括号 []),这意味着您的服务器将返回 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 和参数。该对象有一个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() 策略有很多差异(因为您可以拆分它并创建自己的值图)。

【讨论】:

  • 是的,第三个选项是我最终使用的。在操纵数据方面,它给了我最大的灵活性(尽管不多)。我会将这个答案标记为已接受。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多