Leandro Miranda Rios
Leandro Miranda Rios

Reputation: 135

Spring Boot deserialization snake case to camel case fails. Can't deserialize "some_value" to "someValue"

So I have this Spring Boot app that has to take a GET request with parameters "some_value=1500&some_other_value=50000" to an object with the attributes someValue and someOtherValue.

I've tried @JsonProperty("some_value") and it didn't work. I've added "spring.jackson.property-naming-strategy=SNAKE_CASE" to my application.properties file and it still doesn't work.

Important detail: when I try to serialize an object it does turn someValue => some_value and someOtherValue => some_other_value. So I know the config is "fine" but it refuses to map the request param in snake case to the camel case that I need. (And no... I have no control over the request format. I get the params in snake case and I have to map them to camel case)

Please help. Thanks!

Upvotes: 2

Views: 15077

Answers (5)

Hani
Hani

Reputation: 31

I was looking for a solution for a similar problem, but as I have not found any simple, satisfying answer (most of them with a lot of code, or requiring me to change HTTP methods) I have figured out a way that satisfied me.

If you have a controller method like this:

@GetMapping
public ResponseEntity<Connection> handle(SampleDTO sample)

Then you can use @ConstructorProperties over constructor in SampleDTO to resolve the issue:

public class SampleDTO {
    String testVarOne;
    String testVarTwo;

    @ConstructorProperties({ "test_var_one", "test_var_two" })
    public SampleDTO(String testVarOne, String testVarTwo) {
        this.testVarOne = testVarOne;
        this.testVarTwo = testVarTwo;
}

Of course, this works only if you know the request params names. Works for POST an GET methods.

Upvotes: 0

Kent
Kent

Reputation: 439

I think u have some mistake in configuration , in my case the cause is @EnableWebMvc annotation, if u use this annotation , the default autoconfig from spring boot will not work.

in order to convert attribute from camelCase to snake_case , u only need to override bean jackson2ObjectMapperBuilder ,spring use this bean to change the snake_case setting for jackson , try these code below add them to ur @Configuration file

@Bean
    @Primary
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        Jackson2ObjectMapperBuilder jsonBuilderConfig = new Jackson2ObjectMapperBuilder();
        jsonBuilderConfig.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        return jsonBuilderConfig;
    }

if u read readJavaType() method which is under AbstractJackson2HttpMessageConverter class,u can see how jackson works , there is an objectMapper there used to convert json.track this objectMapper,if PropertyNamingStrategy doesn`t have snake_case setting the convertion cant work

Upvotes: 3

Carvo
Carvo

Reputation: 51

Another way is to create a HandlerMethodArgumentResolver. It is verbose, but you can deal with the query string, inject the ObjectMapper (keeping the same config) and do yourself the conversion.

I created an Annotation to filter which type I wanna handle:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface QueryStringArgResolver {
}

Then created the resolver:

@Component
public class QueryStringArgumentResolver implements HandlerMethodArgumentResolver {

...

    @Autowired
    private ObjectMapper mapper;

    @Override
    public boolean supportsParameter(final MethodParameter methodParameter) {
        return methodParameter.getParameterAnnotation(QueryStringArgResolver.class) != null;
    }

    @Override
    public Object resolveArgument(final MethodParameter methodParameter,
                                  final ModelAndViewContainer modelAndViewContainer,
                                  final NativeWebRequest nativeWebRequest,
                                  final WebDataBinderFactory webDataBinderFactory) throws Exception {

        final HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
        final String json = qs2json(request.getQueryString());
        final Object a = mapper.readValue(json, methodParameter.getParameterType());

        return a;
    }

...
}

Usage:

@ResponseStatus(HttpStatus.OK)
    @GetMapping("/some-url")
    public SomeResponse doSomething(@QueryStringArgResolver final SomeQueryStringToBind request) {
        ...
    }

BTW, don't forget to register your resolver in this way to be able to inject beans:

@Configuration
public class ArgumentResolverConfig implements WebMvcConfigurer {
    @Autowired
    private QueryStringArgumentResolver argumentResolver;

    @Override
    public void addArgumentResolvers(
            final List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(argumentResolver);
    }
}

Upvotes: 3

Min Hyoung Hong
Min Hyoung Hong

Reputation: 1202

First, your request parameters (some_value=1500&some_other_value=50000) are mapped to RequestParam, not RequestBody. Second, jackson library deserializes RequestBody, not RequestParam. Third, Post or Put http method mapping parameter to body.

So, you are requesting GET method and request parameters and your jackson config does not work to your request parameters.

Camel case parameter, or @RequestMapping(name="some_param") String someParam will be mapped to your request paramter.

Upvotes: 0

Leandro Miranda Rios
Leandro Miranda Rios

Reputation: 135

So @Barath was right you don't get clean deserialized objects from GET params. But There's a workaround. Not sure how legal this is or not... but here it goes:

@Autowired private ObjectMapper objectMapper;

@GetMapping("/users")
    public User users(@RequestParam Map<String,String> params){
        User u = objectMapper.convertValue(params,User.class);
        return u;
    }

That way you still have your GET method and you get all the params deserialized into a nice little object for you to do whatever. Hope this helps anyone. And I hope Spring/Jackson people enable auto-deserialization of GET params the same way it works with POST methods.

Upvotes: 1

Related Questions