我尝试将Gson配置为我的JSON映射器以接受“snake_case”查询参数,并将它们转换为标准Java“camelCase”参数。
首先,我知道我可以使用@SerializedName
注释来自定义每个字段的序列化名称,但这将涉及一些手动工作。
在做了一些搜索之后,我相信以下方法应该有效(如果我错了,请纠正我。)
spring.http.converters.preferred-json-mapper=gson
在创建GsonHttpMessageConverter
之前配置Gson as described here
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
时,可以设置它,这意味着我的设置无法按预期工作。
任何提示都将受到高度赞赏。提前谢谢!
答案 0 :(得分:1)
这是一个很好的问题。如果我理解Spring MVC如何在幕后工作,那么@ModelAttribute
驱动的HTTP转换器就不会被使用。从ExampleParam
构造函数或ExampleParam.setCamelCase
方法(首先是de Lombok)抛出异常时可以很容易地检查它 - Spring使用它的bean实用程序使用public
(!){{ 1}}设置DTO值。另一个证据是,无论您的Gson转换器配置如何,都不会调用ExampleParam.setCamelCase
。所以,你的Gson.fromJson
会让你感到困惑,因为默认的Gson实例和Spring一样使用这个策略 - 所以这只是一个混乱的问题。
为了使其工作,您必须创建一个自定义的Gson感知camelCase
实现。假设我们只支持POJO(不是列表,地图或基元)。
HandlerMethodArgumentResolver
所以,结果如下:
@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;
}
}
=&gt; (空) http://localhost:8080/?camelCase=hello
=&gt; http://localhost:8080/?camel_case=hello