I am using Jackson to serialize and deserialize data for a RESTful API. I'd like to have a REST resource (/comments) that allows to POST comments as well as to GET a list of comments.
Here's a (simplified) example of what gets posted to /comments.
{"text":"Text","author":"Paul","email":"paul#example.org"}
Here's what the result of GET /comments should look like:
[{"text":"Text","author":"Paul","emailHash":"76w0kjKP9HpsdhBjx895Sg=="}]
Since email addresses shouldn't be visible to anyone, I decided to return only a MD5 hash of the email addresses in the response.
I have created a simple POJO class Comment that has fields with getters and setters for text, author, email, and emailHash.
Now, when I serialize the result, what I get is the following:
[{"text":"Text","author":"Paul","email":null,"emailHash":"76w0kjKP9HpsdhBjx895Sg=="}]
But I really don't like email to be returned as null here. It rather shouldn't be included at all.
Using the annotation #JsonIgnore on that field will also ignore it on deserialization. Do I have to create two classes, say CreationComment and ResultComment with a super-class Comment that shares common fields or is there a way that avoids creating additional classes?
You don't have to create 2 classes at all. With Jackson you have full control of the behavior of a property during serialization and deserialization using annotations, with #JsonIgnorein the getter you prevent the property from being serialized in your Json response and using #JsonProperty annotation in the setter the property will be set during deserialization. The code will look like this:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Comment {
private String author;
private String email;
#JsonIgnore
public String getEmail() {
return email;
}
#JsonProperty
public void setEmail(String email) {
this.email = email;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Comment comment = new Comment();
comment.setAuthor("anAuthor");
comment.setEmail("email#example.com");
try {
System.out.println(objectMapper.writeValueAsString(comment));
String json = "{\"author\":\"anAuthor\",\"email\":\"another#email.com\"}";
Comment fromJson = objectMapper.readValue(json, Comment.class);
System.out.println("Result from Json: author= " + fromJson.getAuthor() + ", email= " + fromJson.getEmail());
} catch (Exception e) {
e.printStackTrace();
}
}
}
The output after running the main() method to test the solution:
{"author":"anAuthor"}
Result from Json: author= anAuthor, email= another#email.com
Hope it helps,
Jose Luis
You can put #JsonIgnore on getEmail to prevent it from being serialized to JSON and use #JsonCreator to indicate to Jackson a constructor to use for deserialization. The constructor would then only accept an email property and would hash and assign to your emailHash field.
You can put a #JsonInclude annotation on your Comment class to prevent serialization of null fields too.
Your class would probably end up looking something like this:
#JsonInclude(Include.NON_NULL)
public class Comment {
private final String email;
private final String emailHash;
#JsonCreator
public Comment(#JsonProperty("email") String email) {
this.email = email;
this.emailHash = MD5.hash(email);
}
#JsonIgnore
public String getEmail() {
return email;
}
public String getEmailHash() {
return emailHash;
}
}
Related
I am following this article https://quarkus.io/guides/rest-client to build a REST Client to parse the output from the restcountries.eu service.
Here the class holding the model:
public class Country {
public String name;
public String alpha2Code;
public String capital;
public List<Currency> currencies;
public static class Currency {
public String code;
public String name;
public String symbol;
}
}
Now, suppose I would like to add a custom fields such as timestamp, to record the instant when this object has been created. I imagine, I would go ahead and add another field like below:
public class Country {
public String name;
public String alpha2Code;
public String capital;
public List<Currency> currencies;
public Instant timestamp; //<--------- added attribute
[....]
My question is: how do I tell the client to populate that field? Normally, I would have done it in the constructor. However, I could not find docs that explain this part.
Thanks for your help
Simone
You can actually do this in the default constructor. Frameworks like JSONB or Jackson expect POJOs to have a default constructor. They will call it when they create an instance of Country.
Use the #JsonbTransient or #JsonIgnore annotations to mark that attribute of your POJO as ignorable in order to avoid the unmarshaller complaining about attributes that cannot be found in the response.
#Data
public class Country {
private String name;
private String alpha2Code;
private String capital;
private List<Currency> currencies;
#JsonbTransient // if you're using JSONB (default in Quarkus)
#JsonIgnore // if you're using Jackson
private Instant timestamp;
public Country() {
this.timestamp = Instant.now();
}
PS The #Data annotation is something you should consider using. Encapsulation is not a bad thing but creating getters/setters is tedious. But Project Lombok certainly helps here.
I am using Spring #RequestBody to map a JSON payload to a Java Object. Unfortunately this JSON payload does not use a set convention but rather has names that use both camelCase and snake_case.
To be clear my Controller looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
... code here ...
}
with the MobileDeviceData Entity object having several setter methods like:
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setFlagId(int flagId) {
this.flagId = flagId;
}
This works great and without any extra effort when the JSON objects name is camelCase. However for snake_case names I need to add the Annotation:
#JsonProperty("flag_id")
private int flagId;
in order for it to be picked up.
I know it's not a good idea to use the #JsonProperty if it can be avoided as you then will need to annotate every parameter. My question is, is there a more general way to enforce matching snake_case with the corresponding camelCase in the Entity object? And obviously to do it without screwing up the ones that are already camelCase.
As per the article here, there is a simple approach to deserialize the MobileDeviceData class. Here is the sample code as below:
#JsonDeserialize(using = UserDeserializer.class)
public class User {
private ObjectId id;
private String username;
private String password;
public User(ObjectId id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public ObjectId getId() { return id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}
Assume User is the class we’re interested in writing the Deserializer for. Not much is notable here, except for the annotations that tell Jackson who knows how deserialize this class.
public class UserDeserializer extends JsonDeserializer {
#Override
public User deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return new User(null,
node.get("username").getTextValue(),
node.get("password").getTextValue());
}
}
The deserializer is created by extending Jackson’s abstract JsonDeserializer class, and giving it the type we want to deserialize to. Difficult is figuring out that you can reference the JSON by field name with the JsonParser's ObjectCodec.
I hope it helps.
Please feel free to comment if needed!
Having been working on this a bit, I now realize doing anything like what was requested would be counterproductive.
When you receive (deserialize) a JSON Object, it is generally expected that you will deliver (serialize) with the same parameters. If an implementation extracted both camelCase and underscore parameters the same way, then it would not know how to deserialize correctly later on. By following a standard convention and then using #JsonProperty for all the exceptions, it remains possible to deserialize and later deliver the JSON object just as it was received.
I know it's possible to ignore fields if they are null or if they are empty, but is it possible to ignore a field, for example if it is a String, and contains a certain substring?
This is possible if you e.g. use a combination of #JsonIgnore and a Converter.
If you assume the following Person POJO:
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Person {
private final String email;
private final String name;
public Person(final String name, final String email) {
this.name = name;
this.email = email;
}
// Will use special conversion before serializing
#JsonSerialize(converter = EmailConverter.class)
public String getEmail() {
return email;
}
// Will simply use default serialization
public String getName() {
return name;
}
}
In the POJO you define that only non-empty values should be included. Furthermore, it is declared that a specific converter is to be used for the email property. The converter can be defined like this:
public class EmailConverter extends StdConverter<String, String> {
#Override
public String convert(final String value) {
return Optional.ofNullable(value)
.filter(email -> email.length() > 0)
.filter(email -> email.contains("#"))
.orElse(null);
}
}
Note that the converter uses Optional which is a java-8 feature but any validation code will do just fine. When null is returned it is simply skipped since it was declared that way in the Person class.
For more info, check out the JavaDocs for Converter and #JsonSerialize.
I am rookie in Java Annotation and have been searching for applying single annotation on multiple variable simultaneously.
Code:
#Document(collection = "users")
public class User {
private ObjectId id;
#NotNull
private String email;
private String imageURL;
private String authToken;
private Date createdDate;
private Date updateDate;
private boolean isActivated;
private int credits;
.....getter/Setter Method
I want to apply #NotNull property on email, imageURL and authToken too. I can do it by writing #NotNull to each variable but not preferring. How to do it?
#NotNull annotation can be applied at element not at group of elements.
JavaDoc: The annotated element must not be null. Accepts any type.
If you really want to get away with boiler plate code, you can use frameworks like Lombok which can help you to certain extent.
Link : http://projectlombok.org/features/Data.html
OR you can use reflection to validate all the method.
for (Field f : obj.getClass().getDeclaredFields()) {
f.setAccessible(true); // optional
if (f.get(obj) == null) {
f.set(obj, getDefaultValueForType(f.getType()));
// OR throw error
}
}
Java does not support multiple annotation of this type. But you can write something like this
Create a class with annotated field.
Create setters and getters to access the field.
Create all your name,email field as instance of this class.
This way fields will implicitly annotated as NotNull.
public class NotNullString {
#NotNull
String str;
public void set(String str)
{
this.str = str;
}
public String get()
{
return this.str;
}
}
NotNullString name;
NotNullString email;
I am trying to get a method in springmvc to accept a partial json string of an object, and have jackson automatically de-serialize it for me.
I can solve it by making a temporary object with only the attributes I want, or pass it in as a string and use Gson to desearialize it for me, but these feel hacky. Is there any way to tell jackson how to do it?
Here is the controller snippet:
#RequestMapping(value = "/task",
method = RequestMethod.POST,
consumes="application/json")
public #ResponseBody String postTask(#RequestBody Task task){
if(task.getId() == null){
task.setId(UUID.randomUUID());
}
if(task.getDate_entered() == 0){
task.setDate_entered(System.currentTimeMillis());
}
TaskDao.addTask(task);
return "success";
}
And the task, a basic pojo:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class Task {
private UUID id;
private String name;
private String description;
private long date_entered;
private long finish_by;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
// Rest of the getters and setters
}
If you can't tell by my other spring related questions, I'm kind of flying blind, and can't figure out the proper google query for this one.
Thanks!
You need to use #JsonIgnoreannotation of jackson on the method (on setter for deserialization and on getter for serialization) or field, for which you want to ignore serialization and/or deserialization. e.g.
In your example, if you don't want to serialize description, then you can do,
#JsonIgnore
public void setDescription(String description) {
this.description = description;
}
And you will see, that you won't get value of description in converted model.