Saturday, February 13, 2016

Polymorphic endpoints with Jackson

When you are designing an application model, one of the most useful techniques is the polymorphism. This allow to us , to write more generic code, more maintainable, and sometimes OCP  complaint (Open Close Principle, see SOLID).

The same concept, applies when you are designing a Rest API. In other words, the same endpoint can admit different Json structures. With this technique, you avoid to create a different endpoints for the different Json formats. Then your Rest API is more confortable and more friendly for your clients.

Imagine you have to design and endpoint to create task. But you have to be able to create user task and group task. The first step is to design our request model for this endpoint.

public interface CreateTaskRequest {
String getTitle();
String getDescription();
TaskType getType();
}

As you can see down, with the annotation @JsonTypeInfo we are telling to Jackson (Jackson is the library used in this example to convert Json files to java objects) what property has to use to find the class to deserialize. When the property informed in the Json input file is "group", jackson tries to deserialize to GroupCreateTaskRequest object or to GroupCreateTaskRequest when is "user". On the other hand, with the annotation @JsonSubType we are mapping the type property value with the child class.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "taskType")
@JsonSubTypes({ @Type(value = UserCreateTaskRequest.class, name = "user"), @Type(value = GroupCreateTaskRequest.class, name = "group") })
public abstract class AbstractCreateTaskRequest implements CreateTaskRequest {
private String title;
private String description;
private TaskType taskType;
public AbstractCreateTaskRequest(TaskType taskType) {
this.taskType = taskType;
}
@Override
public String getTitle() {
return this.title;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public TaskType getType() {
return this.taskType;
}
public void setTitle(String title) {
this.title = title;
}
public void setDescription(String description) {
this.description = description;
}
}
public class UserCreateTaskRequest extends AbstractCreateTaskRequest {
private String userName;
public UserCreateTaskRequest() {
super(TaskType.USER);
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
public class GroupCreateTaskRequest extends AbstractCreateTaskRequest {
private String groupName;
public GroupCreateTaskRequest() {
super(TaskType.GROUP);
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
Finally, our Rest controller looks like that.

@RestController
@RequestMapping("/task")
public class TaskController {
@RequestMapping(method = RequestMethod.POST)
public void createTask(@RequestBody CreateTaskRequest createTaskRequest) {
if (TaskType.GROUP.equals(createTaskRequest.getType())) {
System.out.println("Creating group task");
}
if (TaskType.USER.equals(createTaskRequest.getType())) {
System.out.print("Creating user task");
}
}
}


You can find the code of this example here.