Jackson
Jackson is a JSON library created and maintained by Faster:XML. “Nobody does bits, bytes, buffers, and pointy braces better”, says their website. Because of its ability to convert Java objects to JSON in a most efficient way it has become part of the Spring Framework. There are alternatives, Jakob Jenkov provides a list.
Where it is used
I encountered Jackson when trying to understand the difference between @RestController and @Controller and the difference between using @RequestMapping and the more specific @GetMapping, @PostMapping etc. When you create a REST api in Spring, you do not return a view to the client but a JSON object. The MVC (Model-View-Controller) model looses the ‘view’ part where normally html pages (‘views’) are generated, either static or dynamically with templates. Instead of a view, you return an object, or more specifically the instance members of an object, in the form of a map of key-value pairs, all in JSON format.
To indicate that you return JSON instead of html you must use @RestController instead of @Controller. Less convenient is to use @Controller and add @ResponseBody to every method that you want to return JSON instead of html.
How Jackson creates JSON from objects
Jackson uses reflection to get the content of object fields, which means that private instance variables will be included. Jackson has a preference for getters and setters (setters for deserialization I suppose). By default the name of the variable as included in the JSON object is the name of the get method, minus get, and starting with lowercase. On the Oracle blog site I found an article of Ben Evans from whom I copied this basic code example:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
}
var grant = new Person("Grant", "Hughes", 19);
var mapper = new ObjectMapper();
try {
var json = mapper.writeValueAsString(grant);
System.out.println(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
The output is:
{"firstName":"Grant","lastName":"Hughes","age":19}
The ObjectMapper class
In this process ObjectMapper, a Jackson class, plays a central role. See here for its source code or here for fasterXML’s own GitHub repositories.
ObjectMapper contains all sorts of methods to create JSON from objects and vice versa. The code above from Ben Evans shows ObjectMapper in action. ObjectMapper is a huge class with a ton of methods and fields, 3900 lines of code, and I have not encountered a good overview of the most relevant methods yet. Let’s say you can probably do anything you want with it.
Customizing the JSON output
You might not want to include every field in an object part of the JSON output. To exclude fields, you can do two things. You can create a new class, a so called Data Transfer Object, that only has the fields you want to include. It might require some copying but you get exactly what you want.
Another method is to use annotations. This doesn’t work as straightforward as it looks like. Below is a sample from the Spring docs:
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
The @JsonView(User.WithoutPasswordView.class)
annotation limits the serialization to the username field. The weird thing is the Class<?> type as parameter for @JsonView. In fact, according to the source code this is the annotation code:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD,
ElementType.PARAMETER,
ElementType.TYPE // to indicate "default view" for properties
})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonView {
/**
* View or views that annotated element is part of. Views are identified
* by classes, and use expected class inheritance relationship: child
* views contain all elements parent views have, for example.
*/
public Class<?>[] value() default { };
}
So what you have to do is to create classes and use them as values, and by nesting and extending you can create different scopes. I assume that the fact that Jackson is refelection based makes it more logical to use Class<?> in this manner. As said before, I need to study reflection to get a better feel for it.
Renaming field names
You might have class instances that you want to be serialized but somehow the field names are not pretty or appropriate. Changing them might break your code somewhere so you shouldn’t do that. Creating a new DTO class and copy values is too cumbersome. In this case you can use the @JsonProperty annotation on the fields with the name you would like in the Json. The good thing is that it is easy and that it communicates to everyone that there are two names for the same field. Ben Evans in his blog post gives this example:
public class Person {
@JsonProperty("first_name")
private final String firstName;
@JsonProperty("last_name")
private final String lastName;
private final int age;
private final List<string> degrees;
public Person(String firstName, String lastName, int age, List<string> degrees) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.degrees = degrees;
}
// ... getters for all fields
}
The code produces something like:
{
"age" : 19,
"degrees" : [ "BA Maths", "PhD" ],
"first_name" : "Grant",
"last_name" : "Hughes"
}
Concluding
I got a little bit in a rabbit hole with this but it was worth the effort. JSON is important, Jackson is part of Spring and I need to get better at the REST/API thing.
Ben Evans got way deeper in the rabbit hole, check his blog post on the Oracle site to learn more about this topic.