15

I have an entity:

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
    private List<Genre> genre;

}

Then I have a controller whose purpose is to retrieve books, my problem is that, the genre field is being included in the json response of my controller. Any way I can exclude those fields that are lazy loaded when jackson serializes the object?

This is the configuration of my ObjectMapper:

Hibernate4Module hm = new Hibernate4Module();
hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
registerModule(hm);
configure(SerializationFeature.INDENT_OUTPUT, true);

Thanks!

I can't mark it as JsonIgnore, as it will be forever out of the serialization box. There will be times where I will need to retrieve the genres along with the book, and by then I will use "fetch join" on my query so it will not be null.

lorraine batol
  • 6,001
  • 16
  • 55
  • 114

5 Answers5

11

You can do this with the Jackson @JsonInclude annotation.

According to the latest version's javadoc (2.4 right now) you can specify with a simple annotation if to include or not the annotated property if the field value is null or empty.

By default, it's JsonInclude.Include.ALWAYS, and this means that even if your lazily not-loaded values are null, Jackson does include the property.

Specifying to don't include empty or null values can significantly reduce the size of the JSON response, with all the benefits included..

If you want to change this behavior, you can add the annotation at class-level or single property/getterMethod level.

Try to add the following annotations to the fields you don't want to include if empty:

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
private List<Genre> genre;
Davide Rossi
  • 516
  • 2
  • 8
  • 7
    This isn't working for me at a Spring JPA + Spring REST Controller solution. The lazy fetching still occurs when the JSON Serialization happens... – Ronye Vernaes Feb 26 '15 at 14:02
  • 2
    This is not the right answer for JPA 2.1 in combination with EclipseLink! The problem is obviously that Jackson checks the properties for "NON_EMPTY" by calling the getters and that triggers the Lazy Loading! Unfortunately, there is no AccesType "Field" Json Annotation or so, that would help. This is by the way supported by Moxy I think (I think it uses JAXB annotations for this). – Nabi Feb 26 '16 at 16:40
  • 2
    @RonyeVernaes it works for me after adding a `Hibernate5Module` Bean. Please see chrismarx' answer in http://stackoverflow.com/questions/21708339/avoid-jackson-serialization-on-non-fetched-lazy-objects. – Zeemee Jan 02 '17 at 12:10
  • I can confirm that this works with Zeemee's extra info. In Spring Boot you need to add the Jackson Hibernate5 module to your pom.xml and register the bean. – jayb0b Feb 22 '19 at 14:27
5

You can use a spring configuration to disable force lazy loading by default!

@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        Hibernate5Module hibernate5Module = new Hibernate5Module();
        hibernate5Module.configure(Feature.FORCE_LAZY_LOADING, false);
        // Enable below line to switch lazy loaded json from null to a blank object!
        //hibernate5Module.configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
        mapper.registerModule(hibernate5Module);
        return mapper;
    }
}
zbishop
  • 51
  • 1
  • 2
3

You can use Jackson's JSON Filter Feature:

@Entity
@JsonFilter("Book") 
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
    private List<Genre> genre;
} 

@Entity
@JsonFilter("Genre")
public class Genre {
   ...
}

Then in the Controller you specify what to filter:

@Controller
public class BookController {
      @Autowired
      private ObjectMapper objectMapper;

      @Autowird
      private BookRepository bookRepository;
      
       @RequestMapping(value = "/book", method = RequestMethod.GET, produces =  "application/json")
       @ResponseBody
       public ResponseEntity<String> getBooks() {

          final List<Book> books = booksRepository.findAll();
          final SimpleFilterProvider filter = new SimpleFilterProvider();
          filter.addFilter("Book", SimpleBeanPropertyFilter.serializeAllExcept("genre");
          return new ResponseEntity<>(objectMapper.writer(filter).writeValueAsString(books), HttpStatus.OK)
       }
      
}

In this way, you can control when you want to filter the lazy relation at runtime

Tiramonium
  • 557
  • 5
  • 15
Ricardo Veguilla
  • 3,107
  • 1
  • 18
  • 17
  • As edit queue is full now, I should correct one line; the second argument of addFilter() should be **SimpleBeanPropertyFilter**.serializeAll... not **SimpleFilterProvider**.serializeAll... class – Yasin Okumuş Apr 20 '17 at 09:52
  • I had to use the field name "genre" in class Book, not the @JsonFilter("Genre") to exclude the list from my serialization. – Vincent B Jun 21 '21 at 16:46
1

Maybe this is related to a known issue about lazy loading.

I don't use jackson-datatype-hibernate, but what I've done to solve the same problem is to get the persistent collection out of the picture by using a DTO instead of serializing a Hibernate object directly. Tools like Dozer can help you out with that. Alternatively, there's a small utility I wrote to do mappings like this.

If you just want to experiment with what a DTO could look like, you can replace the unloaded persistent collection with a regular empty collection, like books.setGenre(new ArrayList<>()); Unfortunately I don't know of a way to tell if a lazily loaded object has been loaded or not, so you can't do this reassignment automatically. The places where you replace persistent collections would need to be determined by you on a case by case basis.

Jason
  • 7,356
  • 4
  • 41
  • 48
-1

You can use Gson instead of ObjectMapper and while defining the entity mark the field as "transient"

public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
    private **transient** List<Genre> genre;

}

while Deserialization using gson.toJson(book) , Gson will not deserialize that element.