In my last post -
JSON - Jackson to the rescue - I've described quick example of JSON response creating. Let's back to this example and take a deeper look at the created response.
[{"id":1, "benefits":[{"name":"Healthy Employees", "id":1, "type":"HEALTH_COVERAGE", "startDate":1104534000000, "endDate":null}, {"name":"Gold Autumn","id":2,"type":"RETIREMENT_PLAN","startDate":1104534000000,"endDate":null},{"name":"Always Secured","id":3,"type":"GROUP_TERM_LIFE","startDate":1104534000000,"endDate":null}],"firstName":"John"},{"id":2,"benefits":[],"firstName":"Mary"},{"id":3,"benefits":[],"firstName":"Eugene"}]
There are situations, where parts of the data represented by domain entities should remain invisible for the public, and therefore shouldn't be serialized and exposed via JSON. We will try to narrow our response down to make it a little more secure from our point of view - by hiding the Employee's benefits.
First method to achieve this goal is usage of
@JsonIgnore annotation.
@Entity
@Table(name = "EMPLOYEES")
public class Employee implements Serializable {
...
@JsonManagedReference("employee-benefit")
@OneToMany(mappedBy = "employee", cascade = CascadeType.PERSIST)
@JsonIgnore
public List getBenefits() {
return benefits;
}
...
}
The above modification will change the response to:
{"employees":[{"id":1,"firstName":"John"},{"id":2,"firstName":"Mary"},{"id":3,"firstName":"Eugene"}]}
which is exactly what we need - but don't smile too wide yet ;) - consider situation when you want the same entity to be serialized in different ways depending on the request "context". It is very probable that you would like give to customer only the properties necessary in the response, while the admin should see much more in his part of application.
@JsonIgnore is unusable in this case :( - but wait, don't panic yet - here we come to the second solution -
@JsonView annotation
Let's modify the controller preparing the JSON response in the following manner:
@Controller
@RequestMapping("/employee-list.json")
public class EmployeeListController {
private static final String MODEL_KEY_EMPLOYEES = "employees";
@Autowired
private EmployerDAO employerDAO;
private final ObjectMapper objectMapper = new ObjectMapper();
private final MappingJacksonJsonView view = new MappingJacksonJsonView();
public EmployeeListController() {
objectMapper.getSerializationConfig().setSerializationView(Employer.PublicView.class);
view.setObjectMapper(objectMapper);
view.setRenderedAttributes(new HashSet(Arrays.asList(MODEL_KEY_EMPLOYEES)));
}
@RequestMapping(method = RequestMethod.GET)
public View handleGet(@RequestParam("employerId") Long employerId, Model model) {
model.addAttribute(MODEL_KEY_EMPLOYEES, employerDAO.getEmployees(employerId));
return view;
}
}
As you see we will use the Spring Framework's
MappingJacksonJsonView, with our own ObjectMapper instance. In controller's constructor we define which model attributes should be serialized (using
setRenderedAttributes method), and switch serialization view to the
Employer.PublicView. This will force Jackson to serialize only those properties which don't have @JsonView annotation at all, or have @JsonView annotations matching specified view. If we will modify our entities to look like this:
@Entity
@Table(name = "EMPLOYERS")
public class Employer implements Serializable {
public interface PrivateView { }
public interface PublicView { }
...
@Column(name = "BUSINESS_NAME")
@JsonView(PublicView.class)
public String getBusinessName() {
return businessName;
}
@JsonManagedReference("employer-employee")
@OneToMany(mappedBy = "employer", cascade = CascadeType.PERSIST)
@JsonView(PublicView.class)
public List getEmployees() {
return employees;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
...
}
@Entity
@Table(name = "EMPLOYEES")
public class Employee implements Serializable {
...
@JsonManagedReference("employee-benefit")
@OneToMany(mappedBy = "employee", cascade = CascadeType.PERSIST)
@JsonView(PrivateView.class)
public List getBenefits() {
return benefits;
}
@JsonBackReference("employer-employee")
@ManyToOne(optional = false)
@JoinColumn(name = "EMPLOYER_ID")
public Employer getEmployer() {
return employer;
}
@Column(name = "FIRST_NAME")
public String getFirstName() {
return firstName;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
...
}
we will get exactly the same response as for
@JsonIgnore usage, but in a little more flexible way :)
{"employees":[{"id":1,"firstName":"John"},{"id":2,"firstName":"Mary"},{"id":3,"firstName":"Eugene"}]}
The last possibility worth mentioning here is usage of mix-in annotations, let's change our controller once again to show the idea standing behind it.
@Controller
@RequestMapping("/employee-list.json")
public class EmployeeListController {
@JsonIgnoreProperties("benefits")
private static class EmployeePublicView extends Employee {
// Empty by design ...
}
private static final String MODEL_KEY_EMPLOYEES = "employees";
@Autowired
private EmployerDAO employerDAO;
private final ObjectMapper objectMapper = new ObjectMapper();
private final MappingJacksonJsonView view = new MappingJacksonJsonView();
public EmployeeListController() {
objectMapper.getSerializationConfig().addMixInAnnotations(Employee.class, EmployeePublicView.class);
view.setObjectMapper(objectMapper);
view.setRenderedAttributes(new HashSet(Arrays.asList(MODEL_KEY_EMPLOYEES)));
}
@RequestMapping(method = RequestMethod.GET)
public View handleGet(@RequestParam("employerId") Long employerId, Model model) {
model.addAttribute(MODEL_KEY_EMPLOYEES, employerDAO.getEmployees(employerId));
return view;
}
}
As you see it is very similar to the controller above, the difference is new class
EmployeePublicView extending
Employee, and defining that
benefits property will be ignored while serialization (using @
JsonIgnoreProperties annotation). OK, but how this will help us with desired serialization of
Employee instances? - the key factor is
addMixInAnnotations usage - this method allows us to override the annotations defined on
Employee class with those from the
EmployeePublicView! Amazing and effective idea which doesn't need any domain entities modifications (you may safely remove the
@JsonView annotations from the preview example)