6

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?

Kevin Thorne
  • 435
  • 5
  • 19

2 Answers2

4

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.

Kevin Thorne
  • 435
  • 5
  • 19
2

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.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • I'll clarify the annotations I used. I had `@JsonIdentityReference(alwaysAsId=true)` as well as `@Json(De)Serialize(as = User.class)` in hopes for it to create the bean. – Kevin Thorne Jul 18 '16 at 18:02
  • Is forcing serialization as an id absolutely require the referred object to be returned within the JSON? Perhaps I could get the serializer to make the empty `User` object with the ID and attach it along? – Kevin Thorne Jul 18 '16 at 18:13
  • It is not forced, so you absolutely can only use ids, no need for placeholder Object. But if reading data back, you have to be able to somehow get data back -- this may be ok, just wanted to mention since it can be problematic for some use cases. – StaxMan Jul 19 '16 at 18:38
  • Yeah, when you're making the client, you have to keep in mind to keep querying the server for the full data. – Kevin Thorne Jul 19 '16 at 20:27
  • how can I make this work for just embedded objects - here is my question: https://stackoverflow.com/questions/46620019/single-custom-serializer-for-all-embedded-annotated-objects-that-replaces-them-w – Michail Michailidis Oct 07 '17 at 12:26