Question: Why does @JsonUnwrap does not work with custom deserializers?

782 views
Skip to first unread message

Ryon Day

unread,
Mar 22, 2016, 12:11:46 PM3/22/16
to jackson-user
Hello all, some questions about intercepting and transforming an object pre-serialization.

Some background:  

Lately, I have been using the Spring-Data-Rest project, which leverages Jackson for (de)serialization. I have found myself in a situation where I wish to selectively transform a single field in an object prior to serialization and I have lost myself in the weeds searching for an elegant solution. I'd like to clear up my befuddlement about the question in the subject line, describe my situation, what I have attempted so far as well as try to find an elegant solution for what I'm trying to do.

Spring-data-rest uses a "Resource" object that contains the target object in a "content" field annotated with the @JsonUnwrap annotation so that the object's fields are placed directly in the output instead of in a "content" object. This makes sense and works perfectly for many use cases. The output to HTTP requests is the result of the serialization of this object.

The first thing that I tried was to register a BeanSerializerModifier. I did this because I wanted to transform only a single field in the domain object and to leverage default serialization for the rest of the object. This worked perfectly in unit testing but during integration testing, the object appeared as a sub-object of "content" in the final output. So close, yet so far. 

After some research, I found a few Stack Overflow posts describing similar situations. The root cause was that using a custom serializer (or apparently, a BeanSerializerModifier) causes Jackson to ignore the @JsonUnwrap annotation. This is actually in the documentation for the annotation, but I have not yet been able to find out the motivation for disabling functionality like this. I accept that the above method that I tried to use is a non-starter, but I would appreciate a larger picture view of why this is the case.

Currently, I'm trying to implement an "UnwrappingBeanSerializer", however this has come complete with its own problems. Irritatingly, the "serialize" method on the object is final, which means that I cannot intercept the serialize call with my subclass. The most I can do is override the serializeFields method. By then it is too late; the object serialization has begun and apparently I am responsible for serializing every field of a fairly complex object. I cannot seem to figure out how to take advantage of default serialization at this point. For some reason, calling super.serializeFields results in exceptions. 

As a last resort, I'm willing to serialize all fields manually. However, I would prefer to work out a better, more elegant solution. Am I barking up the wrong tree here or is there a better solution?

Thank you very much for any help anyone can offer. I am so very close on this but have been stymied at every turn! 

Tatu Saloranta

unread,
Mar 22, 2016, 9:26:25 PM3/22/16
to jackso...@googlegroups.com
 On Tue, Mar 22, 2016 at 9:11 AM, Ryon Day <ryon...@gmail.com> wrote:
Hello all, some questions about intercepting and transforming an object pre-serialization.

Some background:  

Lately, I have been using the Spring-Data-Rest project, which leverages Jackson for (de)serialization. I have found myself in a situation where I wish to selectively transform a single field in an object prior to serialization and I have lost myself in the weeds searching for an elegant solution. I'd like to clear up my befuddlement about the question in the subject line, describe my situation, what I have attempted so far as well as try to find an elegant solution for what I'm trying to do.

Spring-data-rest uses a "Resource" object that contains the target object in a "content" field annotated with the @JsonUnwrap annotation so that the object's fields are placed directly in the output instead of in a "content" object. This makes sense and works perfectly for many use cases. The output to HTTP requests is the result of the serialization of this object.

The first thing that I tried was to register a BeanSerializerModifier. I did this because I wanted to transform only a single field in the domain object and to leverage default serialization for the rest of the object. This worked perfectly in unit testing but during integration testing, the object appeared as a sub-object of "content" in the final output. So close, yet so far.  

After some research, I found a few Stack Overflow posts describing similar situations. The root cause was that using a custom serializer (or apparently, a BeanSerializerModifier) causes Jackson to ignore the @JsonUnwrap annotation. This is actually in the documentation for the annotation, but I have not yet been able to find out the motivation for disabling functionality like this. I accept that the above method that I tried to use is a non-starter, but I would appreciate a larger picture view of why this is the case.

This is not exactly true, but I can see why it might appear to be so.

When you define a custom serializer/deserializer, you will essentially take responsibility of supporting many different things, including how to handle concept of wrapping/unwrapping. Jackson will send enough information that this is possible, but not necessarily easy -- in fact, unwrapping/wrapping functionality is amongst most PITA things there are.
For serializers it means implement `unwrappingSerializer(...)` call to return a serializer that can omit writing of surround JSON Object.

And why is this up to serializer/deserializer? There isn't anything Jackson in general can do, as types are composable: delegation is necessary. Further, most serializer/deserializers keep minimal state and specifically do not have access to a Tree or Object model of input: all they have are streaming JsonParsers/JsonGenerators; they may (have to) buffer some parts as necessary.

So it is not so much disabling of functionality but rather that custom (de)serializers will need to provide all the handling. There is no way this can be done externally, as (de)serializers have full freedom of choosing what and how they read/write.
 

Currently, I'm trying to implement an "UnwrappingBeanSerializer", however this has come complete with its own problems. Irritatingly, the "serialize" method on the object is final, which means that I cannot intercept the serialize call with my subclass. The most I can do is override the serializeFields method. By then it is too late; the object serialization has begun and apparently I am responsible for serializing every field of a fairly complex object. I cannot seem to figure out how to take advantage of default serialization at this point. For some reason, calling super.serializeFields results in exceptions. 

No, you really should not sub-class BeanSerializer/BeanDeserializer classes. While some of them are non-final, this is not an extension mechanism that should be used unless everything else fails. Some Jackson modules have such needs (for example, XML module); and jackson-databind does something like this internally. But since inheritance is not composable, this does not work well across feature set.
 

As a last resort, I'm willing to serialize all fields manually. However, I would prefer to work out a better, more elegant solution. Am I barking up the wrong tree here or is there a better solution?

I think I may not understand the actual solution well enough. Perhaps sharing bit more of actual Objects in question could help?
For what it is worth, your original approach modifying handlers for individual fields does sound like a better option.
 

Thank you very much for any help anyone can offer. I am so very close on this but have been stymied at every turn! 

Let's hope we can get through the mess and solve your problem.

-+ Tatu +-

 

--
You received this message because you are subscribed to the Google Groups "jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
To post to this group, send email to jackso...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages