Conditional field inclusion in Jackson and Spring Boot

When we write JSON API with spring boot, we often need to customize which fields should be included in our response JSON and which should not.

For example, suppose we've a Model like the following:

public class User {
    private Long id;
    private String name;
    private String password;
    private List<String> children;

    // getters and setters here

}

Now we've the following controller:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping

    public List<User> userList(){
        User user = new User();
        user.setId(10L);
        user.setName("Rafiqunnabi Nayan");
        user.setPassword("abcd");
        user.setChildren(Arrays.asList("Child 1", "Child 2"));

        return Arrays.asList(user);

    }

    @GetMapping("{id}")

    public User userDetails(@PathVariable Long id){
        User user = new User();
        user.setId(id);
        user.setName("Rafiqunnabi Nayan");
        user.setPassword("abcd");
        user.setChildren(Arrays.asList("Child 1", "Child 2"));

        return user;

    }
}

Here is the response:
GET /api/users
[
    {
        "id": 10,
        "name": "Rafiqunnabi Nayan",
        "password": "abcd",
        "children": ["Child 1", "Child 2"]
    }
]

GET /api/users/50

{
    "id": 50,
    "name": "Rafiqunnabi Nayan",
    "password": "abcd",
    "children": ["Child 1", "Child 2"]
}


Now suppose we want to include the password and childrens fields only in details API and not in list API. So what we want is the following:

GET /api/users:
[
    {
        "id": 10,
        "name": "Rafiqunnabi Nayan"
    }
]

GET /api/users/50:
{
    "id": 50,
    "name": "Rafiqunnabi Nayan",
    "password": "abcd",
    "children": ["Child 1", "Child 2"]
}

How to do this?

An elegant way to achieve the above behavior is to use JsonView of Jackson.

Step 1:
We need to declare some static classes or interfaces. These classes will be used like a marker to a certain type of view. Here is an example interface:

public class ApiView {
    public interface ListView {}
    public interface DetailsView extends ListView{}
}

We've declared two interfaces here - ListView and DetailsView. We'll use these classes to annotate a view level for our controller methods. DetailsView extends ListView: this means we want to include any attribute which is visible in ListView will be visible in DetailsView

Step 2:
Let's use our views to controller methods:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @JsonView(ApiViews.ListView.class)
    @GetMapping
    public List<User> userList(){
        // same as above
    }

    @JsonView(ApiViews.DetailsView.class)
    @GetMapping("{id}")
    public User userDetails(@PathVariable Long id){
        // same as above 
    }
}

Here we've used the JsonView annotation and our declared interfaces to mark which API is our list view and which one is details view. You can think of it like a context - we're declaring that out userList method in in List context and userDetails method is in Details context. userDetails method is also in List context because DetailsView extends ListView.

Step 3:
Now we need to declare the JsonView annotations to our model attributes so that it can be used by Jackson when desrializing.

public class User {
    private Long id;
    private String name;
    private String password;

    @JsonView(ApiViews.DetailsView.class)
    private List<String> children;

    // getters and setters here
}

In the above code, we're declaring using annotation that we want to include the children attribute when deserialization is done.

Step 4:
We're ready to go! We just need to customize the Jackson mapper a bit so that it includes the attributes that have no JsonView annotation always. Here is the Bean that does this:


@Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization()      {
        return new Jackson2ObjectMapperBuilderCustomizer() {

            @Override
            public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
                jacksonObjectMapperBuilder.featuresToEnable(DEFAULT_VIEW_INCLUSION);
            }

        };
}

Hope this post will help you. Thank you for reading.
Happy Coding!

Comments

  1. Casino, Hotel & Casino, Henderson - MapyRO
    Find your favorite 양산 출장안마 casino in Henderson, 보령 출장샵 Nevada and other nearby places to 의정부 출장마사지 stay closest to Casino, Hotel and Casino, Henderson. Rating: 전주 출장마사지 2.7 · ‎28 상주 출장안마 votes

    ReplyDelete

Post a Comment

Popular posts from this blog

Run tasks in background in Spring

How to configure Wildfly 10 to use MySQL