22
@GetMapping("item")
public @ResponseBody String get(@ModelAttribute Item item)

Item has the attributes

  • name

  • itemType

When I access /item?name=foo&item_type=bar the item gets populated only with name not with itemType.

I tried a bunch of things to get the itemType attribute mapped from item_type.

  • Added @JsonProperty("item_type") inside Item's itemType attribute. Described here.
  • Added a JackonConfiguration that sets the propertyNamingStrategy to PropertyNamingStrategy.SNAKE_CASE. Described here.
  • Added spring.jackson.property-naming-strategy=SNAKE_CASE to my Spring Boot application.properties file. Described here
  • Added the PropertyNamingStrategy on the Item class level. Described here.

How can I solve this?

Btw. I only have this problem for incoming not outgoing JSON serialization of Item.


Update 04/24/17:

Below a minimal sample to demonstrate the problem: When visiting /item you will see that the 'outgoing' JSON serialization works but when visiting /item/search?name=foo&item_type=bar it does not work for 'incoming' JSON deserialization.

Item

package sample;

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(SnakeCaseStrategy.class)
public class Item implements Serializable {
    private String name;
    @JsonProperty("item_type")
    private String itemType;
    public Item() { }
    public Item(String name, String itemType) {
        this.name = name;
        this.itemType = itemType;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getItemType() {
        return itemType;
    }
    public void setItemType(String itemType) {
        this.itemType = itemType;
    }
}

ItemController.java

package sample;

import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/item")
public class ItemController {
    @GetMapping("search")
    public @ResponseBody Page<Item> search(@ModelAttribute Item probe) {
        System.out.println(probe.getName());
        System.out.println(probe.getItemType());
        //query repo by example item probe here...
        return null;
    }
    @GetMapping
    public Item get() {
        return new Item("name", "itemType");
    }   
}

JacksonConfiguration.java

package sample;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;

@Configuration
public class JacksonConfiguration {
    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        return new Jackson2ObjectMapperBuilder()
                .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
    }
} 

SampleBootApplication.java

package sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SampleBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SampleBootApplication.class, args);
    }
}

application.properties

logging.level.org.springframework=INFO
spring.profiles.active=dev
spring.jackson.property-naming-strategy=SNAKE_CASE

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sample</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>nz.net.ultraq.thymeleaf</groupId>
                    <artifactId>thymeleaf-layout-dialect</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <!-- Spring Boot Actuator displays build-related information if a META-INF/build-info.properties 
                            file is present -->
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                        <configuration>
                            <additionalProperties>
                                <encoding.source>${project.build.sourceEncoding}</encoding.source>
                                <encoding.reporting>${project.reporting.outputEncoding}</encoding.reporting>
                                <java.source>${maven.compiler.source}</java.source>
                                <java.target>${maven.compiler.target}</java.target>
                            </additionalProperties>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
Matthias
  • 7,432
  • 6
  • 55
  • 88
hansi
  • 2,278
  • 6
  • 34
  • 42
  • If you have an @EnableWebMvc annotation somewhere, remove it. Ref: http://stackoverflow.com/questions/40649177/jackson-is-ignoring-spring-jackson-properties-in-my-spring-boot-application – Pär Nilsson Apr 25 '17 at 15:58
  • Don't have any. – hansi Apr 25 '17 at 15:58
  • @hansi, your question here solved my problem. I had a problem where I have a field in the class called --> private int aScoreEarned; which becomes ascoreEarned when it is outputted by the JSON based Restful API. Adding @JsonProperty("aScoreEarned") annotation fixed my problem. You are a life saver man! – Artanis Zeratul Aug 19 '19 at 09:42

4 Answers4

9

Solved by doing the Jackson work without the help of Spring.

@GetMapping("search")
public @ResponseBody Page<Item> search(@RequestParam Map<String,String> params) {
    ObjectMapper mapper = new ObjectMapper();
    //Not actually necessary
    //mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
    Item probe = mapper.convertValue(params, Item.class);

    System.out.println(probe.getName());
    System.out.println(probe.getItemType());
    //query repo by example item probe here...
    return null;
}
hansi
  • 2,278
  • 6
  • 34
  • 42
4

If you are having trouble with the default parameter mapping, or you have an object with complex creation logic, you can try implementing a HandlerMethodArgumentResolver. This will allow you to use your class as a controller method argument and have the mapping done elsewhere.

public class ItemArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().equals(Item.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                              ModelAndViewContainer modelAndViewContainer,
                              NativeWebRequest nativeWebRequest,
                              WebDataBinderFactory webDataBinderFactory) throws Exception {
        Item item = new Item();
        item.setName(nativeWebRequest.getParameter("name"));
        item.setItemType(nativeWebRequest.getParameter("item_type"));
        return item;
    }

}

Then you have to register in in your web application configuration:

@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {

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

}

Now you can use the Item class as a controller method argument, without having to instantiate each object in each method:

@RequestMapping("/items")
public @ResponseBody String get(Item item){ ... }
woemler
  • 7,089
  • 7
  • 48
  • 67
1

If /item?name=foo&item_type=bar url is not coming from any kind of form and If you just want to get name and item_type from your url then,

Try This:

@GetMapping("item/{name}/{item_type}")
public String get(@PathVariable("name") String 
myName,@PathVariable("item_type") String myItemType){

//Do your business with your name and item_type path Variable

}

If you have many path variable even you can try bellow approach as well, here all path variables will be in Map,

@GetMapping("item/{name}/{item_type}")
public String get(@PathVariable Map<String,String> pathVars){

//try something like 
   String name= pathVars.get("name");
    String type= pathVars.get("item_type");

//Do your business with your name and item_type path Variable

}

NOTE: if this is from any kind of FORM then better use POST instead of GET

KAmit
  • 337
  • 3
  • 13
  • I considered these two options, but I do want to get the `@ModelAttribute` mapping to do the work. I have a lot (> 30) of parameters and kept it small for the sake of simplicity. – hansi Apr 20 '17 at 14:22
  • To make us of @ModelAttribute, Make a pojo calss with exactly same filed name as your form's input tag's(I assume that u r using forms) name attribute, then spring will do implicit DataBinding for you and there will be no need to do extra thing any more – KAmit Apr 20 '17 at 17:09
  • As described in my question: That's exactly what I have done. It is not working as expected when it comes to underscore to snake case conversion of the field names. – hansi Apr 20 '17 at 17:42
  • I shared a sample that demonstrates the problem above. – hansi Apr 24 '17 at 17:06
  • Actually There is no need to do those extra things, so u have two options 1)Either change your pojo property to item_type or 2)try changing your getter for your itemType to getItem_type(), because in your case Spring is not able to find the correct getter Method for the property item_type which have defined as itemType in your POJO. And your @JsonProperty will not force spring to use item_type instead of itemType during DI. – KAmit Apr 25 '17 at 05:54
  • Are you saying Spring has only the capability to do the conversion of naming strategies one-way? Is that documented anywhere? Again: I have a lot of fields in `Item` and I am not planning on duplicating the getter methods or changing my naming convention because it means a quick fix for the problem. – hansi Apr 25 '17 at 14:19
  • Actually name attribute in your Form input tag must have same name as your POJO property name, if name is different Spring will not bind the value for that filed and that is the reason why your item_type value is empty – KAmit Apr 25 '17 at 15:00
  • Again, then in other words you are saying that (I correct myself) Jackson can only do one way name strategy conversion? Their documentation at least says otherwise: `and take effect for both serialization and deserialization.` (https://github.com/FasterXML/jackson-docs/wiki/PropertyNamingStrategy) – hansi Apr 25 '17 at 15:13
  • man, Actually you are not trying to understand the actual problem instead u r looking at something else, are u able to understand the difference between serialization , deserialization and DataBinding ? – KAmit Apr 26 '17 at 06:21
1

You can also use HttpServletRequest object to get params

@GetMapping("search")
    public @ResponseBody Page<Item> search(HttpServletRequest request) {
       Item probe = new Item();
       probe.setName(request.getParameter('name'));
       probe.setItemType(request.getParameter('item_type'));

        System.out.println(probe.getName());
        System.out.println(probe.getItemType());
        //query repo by example item probe here...
        return null;
    }
e2rabi
  • 4,728
  • 9
  • 42
  • 69
  • This does not solve the problem. There a large number of parameters and I don't want to hardwire all of them. (The bounty has been auto-assigned) – hansi May 03 '17 at 16:19
  • I said you can "also use" it's not the best solution ! for me I use POST for all my work and I prefer @RequestParams with get but sometimes we need to get params from request ... – e2rabi May 03 '17 at 16:36