To make sure the data being sent back and forth isn't redundant in my RESTful web service, every nested object only has it's ID serialized (A Message's User creator only has userId serialized since both the client and server will already know all the details of the user).
Serialization works perfectly, producing this:
{"messageCreatorUser":"60d01602-c04d-4a3f-bbf2-132eb0ebbfc6","messageBody":"Body", ...}
Problem: Deserialization does not produce a nested object with only its ID. The resulting deserialized nested object is null.
Here are the previously mentioned Message and User objects. Serialization "strategy" was used from the 3rd option specified here: How to serialize only the ID of a child with Jackson.
Message.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "messageId")
public class Message implements Serializable {
// -- Hibernate definitions omitted --
private UUID messageId;
// -----------------------------------------------------------------
// Here is what makes the serializer print out the ID specified on the class definition
#JsonIdentityReference(alwaysAsId = true)
// Here is my attempt to get the User class back when I deserialize
#JsonDeserialize(as = User.class)
#JsonSerialize(as = User.class)
private User messageCreatorUser;
// ------------------------------------------------------------------
// -- more arbitrary properties --
public Message() {
}
public Message(UUID messageId) {
this.messageId = messageId;
}
public Message(String messageId) {
this.messageId = UUID.fromString(messageId);
}
// -- getters and setters --
User.java
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User implements Serializable {
private UUID userId;
// -- other arbitrary properties --
public User() {
}
public User(UUID userId) {
this.userId = userId;
}
public User(String userId) {
this.userId = UUID.fromString(userId);
}
// -- getters and setters --
Expected deserialized object:
Message object =
String messageBody = "Body";
User messageCreatorUser =
UUID userId = 60d01602-c04d-4a3f-bbf2-132eb0ebbfc6;
Actual deserialized object:
Message object =
String messageBody = "Body";
User messageCreatorUser = null;
Like I said, I was hoping for a nested User object to be created with only the ID of 60d01602-c04d-4a3f-bbf2-132eb0ebbfc6
Using:
Wildfly 10.0.Final:
RESTEasy 3.0.15.Final
RESTEasy Jackson 2 Provider 3.0.15.Final
Jackson 2.6.3 (annotations, core, databind, etc)
Why do the results differ?
As the answer here (Jackson deserialize JsonIdentityReference (alwaysAsId = true)) states, using a setter deserializer works well and does not require an obnoxious custom deserializer:
Message.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "messageId")
public class Message implements Serializable {
private UUID messageId;
#JsonIdentityReference(alwaysAsId = true)
private User messageCreatorUser;
// -- other fields and such --
public User getMessageCreatorUser() {
return messageCreatorUser;
}
public void setMessageCreatorUser(User messageCreatorUser) {
this.messageCreatorUser = messageCreatorUser;
}
#JsonProperty("messageCreatorUser")
public void setMessageCreatorUser(String userId) {
this.messageCreatorUser = new User(userId);
}
User.java
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
public class User implements Serializable {
private UUID userId;
// -- other fields --
public User(String userId) {
this.userId = UUID.fromString(userId);
}
}
Do note that in order to use #JsonIdentityReference(alwaysAsId = true), you need to have the #JsonIdentityInfo(...) somewhere.
Not sure I understand the whole situation, but if you just want to force serialization as an id, you can use:
#JsonIdentityReference(alwaysAsId=true)
keeping in mind that deserialization does require that there is some way to resolve that id back to an instance and usually that means the actual full Object should be serialized via some other property reference.
Related
I have a class implementing Serializable, which is mapped to a database table. It looks like this:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#Column(name = "name", nullable = false)
public String name;
#Column(name = "email", nullable = false)
public String email;
#Column(name = "status", nullable = false)
public String status;
}
For the most part, I want all these properties to be included in the JSON. However, there is a specific case where I want to exclude status, but I can't figure out a good way of doing this with Jackson.
My controller looks something like this:
public class UserController {
private final ObjectMapper mapper;
#Inject
public UserController(ObjectMapper mapper) {
this.mapper = mapper;
}
public CompletionStage<JsonNode> getUserList() {
// Get list of Users and return the JSON; with all User properties included
}
public CompletionStage<JsonNode> getUser(Long userId) {
// Get a single user from JPA, in a promise
return userDatabase.get(userId).thenApply(user -> { // user is type User
// Here, I don't want to include "status" in the JSON.
return mapper.valueToTree(user);
});
}
}
So when I do mapper.valueToTree(user), of course, it includes all properties of User, but I want to exclude status in this specific route/function while keeping it included in all other places its serialized.
I know I can use #JsonIgnore to ignore it always, but can I do this just sometimes?
Some solutions I thought of are:
filter through the properties and get rid of status
Copy user over to an ObjectNode and manually remove status
Neither of these seems ideal though, I feel like there has to be a cleaner approach with Jackson.
I have a couple of Hibernate entities stored in a DB that I want to internationalize.
For example my "country" entity:
#Table(name = "country")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "name")
private String name;
}
What I now want to do is enhance the API that handles the retrieval of the countries to return the country entry in the appropriate language. (e.g. adding ?lang=en to the query)
For that I have a country controller:
#RestController
#Api(tags = "Country")
public class CountryController {
private final CountryDao countryDao;
public CountryController(CountryDao countryDao) {
this.countryDao = countryDao;
}
#ApiOperation(value = "View a list of available countries.")
#GetMapping(path = "/entity/countries")
public Iterable<Country> getCountries() {
return countryDao.findAll();
}
}
How would I do that?
I have thought of adding a new i18n table that holds message keys with the available translations.
But in the end I still would like the API to return a simple JSON entry containing only the country name that the user is interested in, without having to attach all available translations.
I tried with AttributeConverter to mark the fields on the entity with #Convert(converter = I18nConverter.class)and search the i18n table for the matching key, but with that approach I don't have access to the specified language in the query...
public class I18nConverter implements AttributeConverter<String, String> {
private final I18nEntryDao i18nEntryDao;
#Override
public String convertToDatabaseColumn(String attribute) {
...
}
#Override
public String convertToEntityAttribute(String dbData) {
...
}
}
This is definitely not a duplicate of Only using #JsonIgnore during serialization, but not deserialization. The problem is the same but in the context of Immutables.
When a model(DTO/DAO) is decorated as an Immutable, I am not able to selectively #JsonIgnore one of the properties during serialization. Suppose that we have a UserDto which is defined as an Immutable as follow
#Value.Immutable
#Value.Style(defaults = #Value.Immutable(copy = false), init = "set*")
#JsonSerialize(as = ImmutableUserDto.class)
#JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {
#JsonProperty("id")
#Value.Default
public int getId() {
return 0;
}
#JsonProperty("username")
public abstract String getUsername();
#JsonProperty("email")
public abstract String getEmail();
#JsonProperty("password")
public abstract String getPassword();
}
I believe it is fair to expect that during serialization we would want to ignore the password from the response of the service.
Without using Immutables if we were working with a simple class, then there are many ways to accomplish this. For example - annotate only the getter with #JsonIgnore. Or if possible define a different accessor method (something that doesn't have the get prefix) and only define the regular setter method... and so on.
If I try the same on the Immutables accessor method for the password as shown below:
#Value.Immutable
#Value.Style(defaults = #Value.Immutable(copy = false), init = "set*")
#JsonSersonIgnoreialize(as = ImmutableUserDto.class)
#JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {
....
#JsonProperty("password")
#JsonIgnore
public abstract String getPassword();
}
then, the generated ImmutableUserDto adds the #JsonIgnore on both the getter and setter as shown below.
#Generated(from = "UserDto", generator = "Immutables")
#SuppressWarnings({"all"})
#ParametersAreNonnullByDefault
#javax.annotation.Generated("org.immutables.processor.ProxyProcessor")
#Immutable
#CheckReturnValue
public final class ImmutableUserDto extends UserDto {
...
...
private final String password;
...
...
/**
* #return The value of the {#code password} attribute
*/
#JsonProperty("password")
#JsonIgnore
#Override
public String getPassword() {
return password;
}
...
...
...
#Generated(from = "UserDto", generator = "Immutables")
#NotThreadSafe
public static final class Builder {
...
...
private String password;
#JsonProperty("password")
#JsonIgnore
public final Builder setPassword(String password) {
this.password = password;
return this;
}
}
}
Serialization will work as expected. The password attribute will be excluded from the JSON. But when I try to de-serialize, I get the following error:
java.lang.IllegalStateException: Cannot build UserDto, some of the required attributes are not set [password]
Which is obvious as Immutables added the #JsonIgnore to the setter as well.
The documentation isn't of much help. In their Things to be aware of section, it just mentions the following regarding #JsonIgnore
If using #JsonIgnore, you should explicitly make an attribute
non-mandatory. In Immutables, an attribute can be declared as
non-mandatory via #Nullable, Optional or #Value.Default which are all
different in their effect and we do not derive anything automatically.
Using #Nullable or Optional or #Value.Default is not of any use in case of fields like password.
I have gone through the issue list on their GitHub page and there is a similar issue but the user was asking for a slightly different use case and using #Nullable could solve the problem which doesn't work in my case.
I have also tried to use one of the answers here. Still resulted in the same error.
It looked like this is not supported by Immutables library. I have created a new issue myself. Once I get some feedback from users on SOF, I will probably create a sscce.
I had to use the suggestion given by #benarena in this comment. However I had to explicitly specify the value attribute of the property along with the Access attribute.
#JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY) solved the problem.
The Immutable class would look like:
#Value.Immutable
#Value.Style(defaults = #Value.Immutable(copy = false), init = "set*")
#JsonSersonIgnoreialize(as = ImmutableUserDto.class)
#JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {
....
#JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY)
public abstract String getPassword();
}
I use play framework 1.2.5.3 (with Hibernate 3.6.10), postgreSql 9.2.9.
I have table requests with column uid that I want to generate by custom rules described in my postgreSql trigger (before insert trigger set uid value).
I want to use generated value in my code. I have following models:
#Entity
#Table(name = "requests")
...
public class Request extends GenericModel {
...
#Column(name = "uid", length = Domains.SHORT_STRING)
public String uid;
...
}
The complexity associated with inheritance models
#Entity
#Table(name = "kindergartenrequests")
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")
public class KindergartenRequest extends Request {
...
private static KindergartenRequest create(...) {
KindergartenRequest request = new KindergartenRequest(...); // put null uid
request._save();
Logger.debug(request.uid); // uid is still null here
...
}
}
I tried following:
Defining getter on uid field:
public String getUid() {
if (this.uid == null) {
this.refresh();
}
return this.uid;
}
Defining post persist callback:
#PostPersist
private void afterSave() {
if (this.uid == null) {
this.uid = Request.em()
.createNativeQuery("SELECT uid FROM requests WHERE id = :id")
.setParameter("id", this.id)
.getSingleResult()
.toString();
}
}
But there is no effect in the KindergartenRequest class.
And I don't want to refresh it manually on each request creation. Like that:
request._save();
request.refresh();
Well actually #PostPersist method works fine. Probably play must be full-restarted after some changes.
I am trying to get a Result of a List, basically a list of entities using Jersey RESTful API (Server and Client)
UserRESTClient client = new UserRESTClient();
ClientResponse response = client.getUsersByType(ClientResponse.class, String.valueOf(userType));
List<User> participants = response.getEntity(new GenericType<List<User>>() {
});
However, the above code does not work if Entity User has a Composite Object, if for instance,
public class User {
private UserId userId;
}
public class UserId {
private int id;
private int categoryId;
}
In this case, the JSON is deserialized by Jersey and returned null for the field type UserId inside Class User. I inspected the JSON returned and everything seems good at the RESTful Server end, but the nested JSON response is not clearly processed at the Client.
Any help would be greatly appreciated. I am not sure if it because of the Jackson preprocessor.
Following is the actual Code Snippet. It involves two classes Participant and ParticipantPK (primary for each Participant).
#Entity
#Table(name = "conference_participant")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Participant.findAll", query = "SELECT p FROM Participant p"),
public class Participant implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected ParticipantPK participantPK;
}
#Embeddable
public class ParticipantPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "conference_id")
private int conferenceId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 150)
#Column(name = "participant_sip_uri")
private String participantSipUri;
public ParticipantPK() {
}
public ParticipantPK(int conferenceId, String participantSipUri) {
this.conferenceId = conferenceId;
this.participantSipUri = participantSipUri;
}
And the Code for retrieving ClientResponse,
List<Participant> participants = response.getEntity(new GenericType<List<Participant>>() {
});
However, the ParticipantPK (Composite PK) is null.
You only pasted a code snippet so I don't know if this part is excluded, but in my code I didn't have setters for the fields. I had getters, but no setters.
Without the setters, my composite objects themselves were non-null, but the members of those objects were themselves null.
I tried to reproduce it, but using the same data structures worked for me. What version of Jersey are you using? Is User class annotated with #XmlRootElement or are you using the POJO mapping feature?