【问题标题】:Configuring snake_case query parameter using Gson in Spring Boot在Spring Boot中使用Gson配置snake_case查询参数
【发布时间】:2017-03-31 17:00:26
【问题描述】:

我尝试将 Gson 配置为我的 JSON 映射器以接受“snake_case”查询参数,并将它们转换为标准 Java “camelCase”参数。

首先,我知道我可以使用@SerializedName注解来自定义每个字段的序列化名称,但这会涉及一些手动工作。

经过一番搜索,我相信以下方法应该有效(如果我错了,请纠正我)。

  1. 使用 Gson 作为 Spring Boot 的默认 JSON 映射器

spring.http.converters.preferred-json-mapper=gson

  1. 在创建GsonHttpMessageConverter之前配置Gson as described here

  2. 根据GSON Field Naming Policy自定义步骤2中的Gson命名策略

private GsonHttpMessageConverter createGsonHttpMessageConverter() {
    Gson gson = new GsonBuilder()
                   .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                   .create();

    GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter();
    gsonConverter.setGson(gson);

    return gsonConverter;
}

然后我像这样创建一个简单的控制器:

@RequestMapping(value = "/example/gson-naming-policy")
public Object testNamingPolicy(ExampleParam data) {
    return data.getCamelCase();
}

使用以下Param 类:

import lombok.Data;

@Data
public class ExampleParam {

    private String camelCase;

}

但是当我使用查询参数?camel_case=hello 调用控制器时,data.camelCase 无法填充(并且它为空)。当我将查询参数更改为?camelCase=hello 时,可以设置它,这意味着我的设置没有按预期工作。

任何提示将不胜感激。提前致谢!

【问题讨论】:

    标签: spring-boot gson


    【解决方案1】:

    这是一个很好的问题。如果我了解 Spring MVC 在幕后是如何工作的,那么@ModelAttribute-driven 不会使用 HTTP 转换器。从ExampleParam 构造函数或ExampleParam.setCamelCase 方法(首先是de-Lombok)抛出异常时,可以轻松检查它——Spring 使用它的bean 实用程序使用public(!)ExampleParam.setCamelCase 设置DTO 值.另一个证据是,无论您的 Gson 转换器如何配置,都不会调用 Gson.fromJson。因此,您的camelCase 让您感到困惑,因为默认的 Gson 实例和 Spring 一样使用这种策略——所以这只是一个混淆问题。

    为了使其工作,您必须创建一个自定义的 Gson 感知 HandlerMethodArgumentResolver 实现。假设我们只支持 POJO(不支持列表、映射或原语)。

    @Configuration
    @EnableWebMvc
    class WebMvcConfiguration
            extends WebMvcConfigurerAdapter {
    
        private static final Gson gson = new GsonBuilder()
                .setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES)
                .create();
    
        @Override
        public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(new HandlerMethodArgumentResolver() {
                @Override
                public boolean supportsParameter(final MethodParameter parameter) {
                    // It must be never a primitive, array, string, boxed number, map or list -- and whatever you configure ;)
                    final Class<?> parameterType = parameter.getParameterType();
                    return !parameterType.isPrimitive()
                            && !parameterType.isArray()
                            && parameterType != String.class
                            && !Number.class.isAssignableFrom(parameterType)
                            && !Map.class.isAssignableFrom(parameterType)
                            && !List.class.isAssignableFrom(parameterType);
                }
    
                @Override
                public Object resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest,
                        final WebDataBinderFactory binderFactory) {
                    // Now we're deconstructing the request parameters creating a JSON tree, because Gson can convert from JSON trees to POJOs transparently
                    // Also note parameter.getGenericParameterType() -- it's better that Class<?> that cannot hold generic types parameterization
                    return gson.fromJson(
                            parameterMapToJsonElement(webRequest.getParameterMap()),
                            parameter.getGenericParameterType()
                    );
                }
            });
        }
    
        ...
    
        private static JsonElement parameterMapToJsonElement(final Map<String, String[]> parameters) {
            final JsonObject jsonObject = new JsonObject();
            for ( final Entry<String, String[]> e : parameters.entrySet() ) {
                final String key = e.getKey();
                final String[] value = e.getValue();
                final JsonElement jsonValue;
                switch ( value.length ) {
                case 0:
                    // As far as I understand, this must never happen, but I'm not sure
                    jsonValue = JsonNull.INSTANCE;
                    break;
                case 1:
                    // If there's a single value only, let's convert it to a string literal
                    // Gson is good at "weak typing": strings can be parsed automatically to numbers and booleans
                    jsonValue = new JsonPrimitive(value[0]);
                    break;
                default:
                    // If there are more than 1 element -- make it an array
                    final JsonArray jsonArray = new JsonArray();
                    for ( int i = 0; i < value.length; i++ ) {
                        jsonArray.add(value[i]);
                    }
                    jsonValue = jsonArray;
                    break;
                }
                jsonObject.add(key, jsonValue);
            }
            return jsonObject;
        }
    
    }
    

    所以,结果如下:

    • http://localhost:8080/?camelCase=hello => (空)
    • http://localhost:8080/?camel_case=hello => "hello"

    【讨论】:

    • 完美运行! HandlerMethodArgumentResolver 对我来说是一个盲区。我可以知道哪些线索可能会引导我找到这个解析器(如果没有你的帮助)?
    • @Wenzhong 对不起,我似乎把你和“幕后”混淆了(实际上是在查看我的代码库时):HandlerMethodArgumentResolver 是一个标准的众所周知的 Spring MVC 接口,经常使用。
    • 谢谢@lyubomyr-shaydariv!我还有很多东西要学。
    • 又遇到一个问题,如果我还想将@PathParam 设置为ExampleParam,我可以知道我应该检查哪个解析器吗?即localhost:8080/resource/:resource_id?camel_case=hello。我想在 ExampleParam 中同时访问 resourceIdcamelCase
    • 更进一步,是否可以将@RequestBody / @PathParam / @Param 合并到ExampleParam 中?
    猜你喜欢
    • 2017-11-18
    • 1970-01-01
    • 2014-11-18
    • 2021-09-15
    • 2020-02-27
    • 1970-01-01
    • 2020-10-21
    • 2020-05-31
    • 1970-01-01
    相关资源
    最近更新 更多