Issue
I have two models:
@Entity
class ModelA {
...
@OneToMany(mappedBy = "modelA")
@JsonManagedReference
private List<ModelB> modelBs = new ArrayList<ModelB>();
...
}
@Entity
class ModelB {
...
@ManyToOne
@JoinColumn(name = "modela_id")
@JsonBackReference
private ModelA modela;
...
}
And a REST/Resource controller for ModelA which has a list method:
@GetMapping("list")
public @ResponseBody ArrayList<ModelA> list() {
ArrayList<ModelA> modelas = new ArrayList();
for (ModelA moa : modelARepository.findAll()) {
modelas.add(moa);
}
return modelas;
}
Now by default the response from that controller will contain a JSON array of ModelA objects but those will also contain ModelB. Is there a way to pick which relations you want serialized and when ?
Coming from Django/Laravel I would be solving this with dedicated serializes for instance I would have a ModelAWithRelatiionsSerializer
where I would serializer ModelA + all of it's relations or ModelAWithModelBSerializer
which would return ModelA serialized with one level of ModelB etc.
I know I can juse ignore annotation to not include it but that's really not the same and I saw nothing regarding serializes in Spring packages. What's the best or optimal way of doing this ?
Solution
For anyone else who is moving from Laravel(PHP), Django(Python), Rails(Ruby):
I created a package serializers
in which I have a serializer class for each of my models.
public class UserSerializer {
// Default user values
public interface User {}
// User values with users posts
public interface UserWithPosts extends User {}
}
Now in your model what you do is annotate fields to which serializers they belong to:
@Entity
class User {
@Id
@GeneratedValue
@JsonView(UserSerializer.User.class)
private Long id;
@JsonView(UserSerializer.User.class)
private String username;
@JsonView(UserSerializer.User.class)
private String email;
@OneToMany(mappedBy = "user")
@JsonManagedReference // This prevents infinite loop
@JsonView(UserSerializer.UserWithPosts.class)
private List<Post> posts;
// .. getters and setters bellow
}
In your controller you than annotate your methods with @JsonView(..)
as to which serializer you want to use.
@RestController
@RequestMapping("users")
public class UsersController {
private UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* In list method we only want users default values to keep the payload smaller.
*/
@GetMapping("list")
@JsonView(UserSerializer.User.class)
public @ResponseBody ArrayList<User> list() {
ArrayList<User> users = new ArrayList<>();
for (User user : userRepository.findAll()) {
users.add(user);
}
return users;
}
/**
* In this method we are only fetching a single user so payload is smaller so we can add the users posts as well. You could also toggle this with a url filter/parameter.
*/
@GetMapping("show")
@JsonView(UserSerializer.UserWithPosts.class)
public @ResponseBody ArrayList<User> show(@RequestParam(name = "id", required = true) Long id) {
return userRepository.findOne(id):
}
}
Configuration! This was pretty easy to understand however when I first did it none of the examples/tutorials told/showed that you need to update the configuration as well. If you don't do it than you will have a problem with serializing your relations the serializers will still work however you would get something like this in your JSON response:
...
"posts": [
{},
{},
{},..
]
...
So it would return a JSON object for each post user has but no values to fix this i created config
package added a WebConfiguration
class:
@Configuration
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().defaultViewInclusion(true).build();
converters.add(new MappingJackson2HttpMessageConverter(mapper));
}
}
You can also do this in XML configuration. But I personally prefer using Java class based configuration. It would be great if spring documentation included better explanations for newcomers.
Answered By - Sterling Duchess
Answer Checked By - Senaida (JavaFixing Volunteer)