I just released Module 10 in the Master Class of “REST with Spring”:

>> THE "REST WITH SPRING" CLASSES

1. Overview

In this tutorial, we’ll go over how to use Jackson JSON Views to serialize/deserialize objects, customize the views and finally – how to start integrating with Spring.

2. Serialize Using JSON Views

First – let’s go through a simple example – serialize an object with @JsonView.

Here is our view:

public class Views {
    public static class Public {
    }
}

And the “User” entity:

public class User {
    public int id;

    @JsonView(Views.Public.class)
    public String name;
}

Now let’s serialize a “User” instance using our view:

@Test
public void whenUseJsonViewToSerialize_thenCorrect() 
  throws JsonProcessingException {
 
    User user = new User(1, "John");

    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);

    String result = mapper
      .writerWithView(Views.Public.class)
      .writeValueAsString(user);

    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("1")));
}

Note how, because we’re serializing with a specific view active, we’re seeing only the right fields being serialized.

It’s also important to understand, that – by default – all properties not explicitly marked as being part of a view, are serialized. We are disabling that behavior with the handy DEFAULT_VIEW_INCLUSION feature.

3. Use Multiple JSON Views

Next – let’s see how to use multiple JSON Views – each has different fields as in the following example:

Here we have to views where Internal extends Public, with the internal view extending the public one:

public class Views {
    public static class Public {
    }

    public static class Internal extends Public {
    }
}

And here is our entity “Item” where only the fields id and name are included in the Public view:

public class Item {
 
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
}

If we use the Public view to serialize – only id and name will be serialized to JSON:

@Test
public void whenUsePublicView_thenOnlyPublicSerialized() 
  throws JsonProcessingException {
 
    Item item = new Item(2, "book", "John");

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper
      .writerWithView(Views.Public.class)
      .writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("2"));

    assertThat(result, not(containsString("John")));
}

But if we use the Internal view to perform the serialization, all fields will be part of the JSON output:

@Test
public void whenUseInternalView_thenAllSerialized() 
  throws JsonProcessingException {
 
    Item item = new Item(2, "book", "John");

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper
      .writerWithView(Views.Internal.class)
      .writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("2"));

    assertThat(result, containsString("John"));
}

4. Deserialize Using JSON Views

Now – let’s see how to use JSON Views to deserialize objects – specifically, a User instance:

@Test
public void whenUseJsonViewToDeserialize_thenCorrect() 
  throws IOException {
    String json = "{"id":1,"name":"John"}";

    ObjectMapper mapper = new ObjectMapper();
    User user = mapper
      .readerWithView(Views.Public.class)
      .forType(User.class)
      .readValue(json);

    assertEquals(1, user.getId());
    assertEquals("John", user.getName());
}

Note how we’re using the readerWithView() API to create an ObjectReader using the given view.

5. Customize JSON Views

Next – let’s see how to customize JSON Views. In the next example – we want to make the Username” UpperCase in the serialization result.

We will use BeanPropertyWriter and BeanSerializerModifier to customize our JSON view. First – here is the BeanPropertyWriter UpperCasingWriter to transform the User name to upper case:

public class UpperCasingWriter extends BeanPropertyWriter {
    BeanPropertyWriter _writer;

    public UpperCasingWriter(BeanPropertyWriter w) {
        super(w);
        _writer = w;
    }

    @Override
    public void serializeAsField(Object bean, JsonGenerator gen, 
      SerializerProvider prov) throws Exception {
        String value = ((User) bean).name;
        value = (value == null) ? "" : value.toUpperCase();
        gen.writeStringField("name", value);
    }
}

And here is the BeanSerializerModifier to set the User name BeanPropertyWriter with our custom UpperCasingWriter:

public class MyBeanSerializerModifier extends BeanSerializerModifier{

    @Override
    public List<BeanPropertyWriter> changeProperties(
      SerializationConfig config, BeanDescription beanDesc, 
      List<BeanPropertyWriter> beanProperties) {
        for (int i = 0; i < beanProperties.size(); i++) {
            BeanPropertyWriter writer = beanProperties.get(i);
            if (writer.getName() == "name") {
                beanProperties.set(i, new UpperCasingWriter(writer));
            }
        }
        return beanProperties;
    }
}

Now – let’s serialize a User instance using the modified Serializer:

@Test
public void whenUseCustomJsonViewToSerialize_thenCorrect() 
  throws JsonProcessingException {
    User user = new User(1, "John");
    SerializerFactory serializerFactory = BeanSerializerFactory.instance
      .withSerializerModifier(new MyBeanSerializerModifier());

    ObjectMapper mapper = new ObjectMapper();
    mapper.setSerializerFactory(serializerFactory);

    String result = mapper
      .writerWithView(Views.Public.class)
      .writeValueAsString(user);

    assertThat(result, containsString("JOHN"));
    assertThat(result, containsString("1"));
}

6. Using JSON Views with Spring

Finally – let’s take a quick look at using JSON views with the Spring Framework. We can leverage the @JsonView annotation to customize our JSON response at the API level.

In the following example – we used the Public view to respond:

@JsonView(Views.Public.class)
@RequestMapping("/items/{id}")
public Item getItemPublic(@PathVariable int id) {
    return ItemManager.getById(id);
}

The response is:

{"id":2,"itemName":"book"}

And when we used the Internal view as follows:

@JsonView(Views.Internal.class)
@RequestMapping("/items/internal/{id}")
public Item getItemInternal(@PathVariable int id) {
    return ItemManager.getById(id);
}

That was the response:

{"id":2,"itemName":"book","ownerName":"John"}

If you want to dive deeper into using the views with Spring 4.1, you should check out the Jackson improvements in Spring 4.1.

7. Conclusion

In this quick tutorial, we had a look at the Jackson JSON views and the @JsonView annotation. We showed how to use JSON Views to have fine-grained control over our serialize/deserialize process – using a single or multiple views.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE COURSE

  • ChangWonSon

    wow~ awesome..

  • Preben Asmussen

    I wonder if it in spring is possible to use eg auth. roles annotations in combination with jsonview without having to duplicate controller methods

  • Kisna

    I don’t find this scalable and more importantly there was no way to control filters/fields dynamically, there are many workarounds for nested property filters on github and looking forward to see Jackson support nested property filters better
    https://github.com/FasterXML/jackson-databind/issues/1040

    https://github.com/krishna81m/jackson-nested-prop-filter

    • Hey Kisna – looks like an interesting potential addition to Jackson and I see that a PR is encouraged. So – if you do merge it into the core, let me know and I’ll cover it here. Cheers,
      Eugen.

  • Wim Deblauwe

    Also interesting is dynamically selecting the view to use in Spring controller: http://stackoverflow.com/questions/28878488/dynamic-selection-of-jsonview-in-spring-mvc-controller

    • Hey Wim – I like that approach, with the one caveat that Jackson specific classes are now bleeding into the public implementation. That may be fine, and if it is – taking advantage of these custom features is certainly a good way to go. Just as long as you’re aware of the trade-off.
      Thanks for the interesting link. Cheers,
      Eugen.

  • Rezaul Karim Sajib

    I use @JsonView to restrict specific field of model to be updated in HttpPost Api method. But, even though it successfully prevent update but could not hide it in swagger ui api method specification. Any thought regarding this?

    • Hey Rezaul,
      You’ll have to use the Swagger specific annotations there – Swagger isn’t able to use and understand the Jackson annotations.
      Cheers,
      Eugen.

  • Lubomir Zrnecko

    Eugen, thanks for this article. On the other hand I’m getting more and more confused by myriads of options and annotation in Jackson. In particular, it seems to me that @JsonView use cases overlap with those of @JsonIgnore which again overlap with the use of @JsonProperty(access = Access.READ_ONLY). If I’m not the only one who struggles with this, it could be a topic for some of your future blogposts. Thanks again.

    • Yeah, Jackson certainly has multiple ways of doing the same thing in some cases. And I fully agree – it’s both super powerful but also a bit annoying. It may make for a good topic, but it’s one that would need a lot of work and research (to get it right), so if I do write about it, it’s probably not going to be soon.
      Cheers,
      Eugen.

  • Mariano Eloy Fernández Osca

    Hi Eugen,

    I’m trying to use @JsonView on @Controllers returning Spring Data’s Page interface. Do you know how to make Jackson serialize it without re-implementing it or turning on DEFAULT_VIEW_INCLUSION?

    Cheers,

    Mariano.

    • Hey Mariano,
      I’m assuming you don’t want to turn that on because it’s global. You can instead register a custom JsonSerializer just for the Page class – that way it doesn’t have any global effect.
      Hope that helps. Cheers,
      Eugen.

      • Mariano Eloy Fernández Osca

        Hi Eugen,

        Certainly, turning DEFAULT_VIEW_INCLUSION on would render all @JsonView annotations pointless, since everything would get serialized every time.

        I must say I tried to register a custom JsonSerializer for Page and PageImpl classes but it does not yield the expected result. Even though a custom serializer is registered, since these classes are not annotated with @JsonView, they do not get serialized.

        I found a workaround. I subclassed PageImpl and annotated it with @JsonView. That worked, but it’s not pretty.
        Here’s the repo about all this: https://github.com/mefernandez/spring-jpa-lazy-projections

        There must be a way to force a non-annotated class to get serialized. Deep diving ahead!

        Cheers,

        Mariano.

        • That’s interesting, because the custom serializer should still work. But of course I’m glad you found a quick solution – not ideal, but OK.
          If you’d like to look further into this one, and you can create a PR for this module with a failing test showing the issue, I’d be happy to have a look at it. Cheers,
          Eugen.

        • Patrick

          Hi Mariano, i’ve exactly the same problem. Using such custom Page classes doesn’t seem clean. Isn’t there any better solution for that? I’m using the pagedResourcesAssembler.

          • Mariano Eloy Fernández Osca

            Hi Patrick,

            I got it working with a custom serializer for Page class, as Eugen suggested, which is far better than subclassing.
            I just needed to register a new module in my Spring Boot @Configuration class.
            Here’s a link to a GitHub Gist with the pieces of Java code.
            https://gist.github.com/mefernandez/8962defd845c08bdaaecf5ba4bd99d11

            That does the trick!
            I’m putting it all together and diving into performance implications in this GitHub repo: https://github.com/mefernandez/spring-jpa-lazy-projections

            Cheers!

            Mariano.

          • Hey Mariano,
            Thanks for taking the time to write up the solution.
            Is there any chance you can replace the code examples with links to the files over on Github? That’s quite a bit of code to display here in the comments.
            Cheers,
            Eugen.

          • Mariano Eloy Fernández Osca

            Hi Eugen,

            Sure, I’m organizing the repo at this time. I’ll come back and edit the comment to replace the code with final links to the GitHub repo.

            Cheers,

            Mariano.

          • Sounds good. Or you can simply open a Github gist or issue and add the code there.

          • Mariano Eloy Fernández Osca

            Done.

  • Christopher Ottersen

    is it possible to specify formatting in a view? ex:
    in view1: I want to use
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = “yyyy-MM-dd’T’HH:mm:ss”)
    whereas in view2:
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = “yyyy-MM-dd”)

    • That’s an interesting question – I’ve never tried to do that, so I’m not sure. In this case, maybe StackOverflow would be a better way to go.
      Cheers,
      Eugen.

  • matt

    Do we really need fully qualified name to use with jsonview? In above example ( first example ), how if we do Jsonview( Public.class ) with import of the actual class enclosing it? Also are there strict rules to use parentheses I have a case where auto generated class has curly brackets inside parentheses like Jsonview ( { Public.class }). Researching on to see how I could get this working with jsonview…any suggestions …

    • Hey Matt,
      The annotation simply accepts an array of Class objects. Everything else is just a good way to structure the views, but not a requirement. As long as you use a Class, you’re fine.
      Cheers,
      Eugen.