I need to write a custom Serializer for one of my entities "Payment" and I have to implement it by extending StdSerializer:
class Payment {
}
class PaymentSerializer extends StdSerializer<Payment> {
public PaymentSerializer() {
this(null);
}
public PaymentSerializer(Class<Payment> t) {
super(t);
}
@Override
public void serialize(Payment value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// some logics
}
}
Since I use Spring, I register this Serializer so Spring could identify it:
@Bean
public Jackson2ObjectMapperBuilder serializersObjectMapperBuilder() {
SimpleModule module = new SimpleModule();
module.addSerializer(Payment.class, applicationContext.getBean(PaymentSerializer.class));
return new Jackson2ObjectMapperBuilder().modules(module);
}
Now I have a controller that returns data back to the client and it uses this Serializer without any problem:
@RestController
@RequestMapping("/payment")
class PaymentController {
@GetMapping
public List<Payment> getAll() {
return Arrays.asList(new Payment());
}
}
Since now, my Serializer works fine and everything is good.
The problem is with another entity "Order" which has Payment as a property with @JsonUnwrapped:
class Order {
@JsonUnwrapped
private Payment payment;
}
I need to unwrap the Payment inside the Order and I want to use the same PaymentSerializer but the problem is when I use this custom Serializer, the @JsonUnwrapped annotation will be ignored and the output will be something like this:
{
"payment": {
.....
}
}
As I mentioned, I want to eliminate the "payment" field and unwrap it.
I know that for emulating @JsonUnwrapped for a custom Serializer I need to extend UnwrappingBeanSerializer class, but as I mentioned at first, I need the standard Serializer too.
Changing my entity models is not an option for me.
Is there any way to accomplish this?
I use Spring Boot 2.1.3.RELEASE which I believe uses Jackson 2.9
Only standard POJOs handled with BeanSerializer / BeanDeserializer support use of @JsonUnwrapped without extra work. If you want it to work for custom types (ones with custom (de)serializers, you will need to do more work.
Custom (de)serializers typically have to add explicit support for any and all annotations.
For serializers, you need to implement JsonSerializer.unwrappingSerializer() and for deserializers JsonDeserializer.unwrappingDeserializer(): these are factory methods that create instances that will handle all the complexities of dealing with changing structure.
I would not recommend extending UnwrappingBeanSerializer; it is an internal class and unlikely to work for your case.
that's a good clue.
My use case is that inside the serialize method, I just transform the input Payment value and then use the default registered Serializer to serialize that Payment (transformPayment method):
@Override
public void serialize(Payment value, JsonGenerator gen, SerializerProvider provider) throws IOException {
entitySerializerHelper.getDefaultObjectMapper().getSerializerProviderInstance().defaultSerializeValue(transformPayment(value), gen);
}
private Payment transformPayment(Payment value) {
// some logic
}
Is it possible to implement unwrappingSerializer() in a similar way or I have to create and return a new JsonSerializer in the unwrappingSerializer() method?
@cowtowncoder could you advise?
@MJ-DEV91 I don't quite understand what you are asking here. Implementation for whatever unwrappingSerializer() returns can be anything: it just allows you to provide different handling for such use case.
On serialize() method: not sure why it uses a complex way of looking for a SerializerProvider when one is being passed as argument, so maybe just use
provider.defaultSerializeValue(transformPayment(value), gen);
@cowtowncoder if I use provider.defaultSerializeValue(transformPayment(value), gen); (in the serialize method), then there would be a StackOverflow, because the same method is going to be called infinitely. basically, I registered the default serializer and then use it in my custom Serializer. The same thing I want to do in the unwrappingSerializer() method.
just assume that I have an ObjectMapper in my class PaymentSerializer (as a class field) and I want to hand over the unwrapping serialization to this ObjectMapper (with its default behavior for @JsonUnwrapped annotation) in the unwrappingSerializer() method. is there any way to do that?
In case someone needs help understanding how to implement a serializer supporting unwrapping, here's what you need to do (code shown in Kotlin syntax):
internal object JSStringSerializer : StdSerializer<JSString>(JSString::class.java)
{
override fun serialize(value: JSString, gen: JsonGenerator, provider: SerializerProvider?)
{
gen.writeStartObject()
writeContents(gen, value)
gen.writeEndObject()
}
override fun unwrappingSerializer(unwrapper: NameTransformer?): JsonSerializer<JSString>
{
return object : JsonSerializer<JSString>()
{
override fun serialize(value: JSString, gen: JsonGenerator, serializers: SerializerProvider?)
{
writeContents(gen, value)
}
override fun isUnwrappingSerializer() = true
}
}
private fun writeContents(gen: JsonGenerator, value: JSString)
{
// write the object entries
gen.writeFieldName("example")
gen.writeString(value.value)
// ...
}
}
EDIT: at least this worked for my use case... not sure if I should've used the NameTransformer somehow but it did not seem to be required.
@cowtowncoder if I use
provider.defaultSerializeValue(transformPayment(value), gen);(in theserializemethod), then there would be aStackOverflow, because the same method is going to be called infinitely. basically, I registered the default serializer and then use it in my custom Serializer. The same thing I want to do in theunwrappingSerializer()method.
just assume that I have anObjectMapperin my classPaymentSerializer(as a class field) and I want to hand over the unwrapping serialization to thisObjectMapper(with its default behavior for@JsonUnwrappedannotation) in theunwrappingSerializer()method. is there any way to do that?
@cowtowncoder is it possible to get the default unwrapping serializer out of an ObjectMapper? something like this:
myObjectMapper.getDefaultUnwrappingSerializer().serialize(myObject);
@MJ-DEV91 No, "default" serializer always refers to what ObjectMapper considers serializer to use for given type, and if you do register custom implementation on mapper (but NOT if it's for property via annotation), that becomes the default.
There is a way to access original out-of-the-box default, however, but that requires implementing BeanSerializerModifier, registering that with mapper. If so, a default Bean serializer will be constructed (assuming there is no custom serializer registered), and you can create your own implementation but pass the default one to it.
This is the intended way for creating delegating serializers that in some cases need to use default one, in other cases not.
If doing this, your custom implementation will also need to implement ResolvableSerializer and ContextualSerializer callbacks: in both cases you will need to delegate to "default" serializer these calls.
@renatoathaydes NameTransformer is only needed if unwrapper specifies "prefix" to use, so if one is not added, it may be ignored.
Most helpful comment
Only standard POJOs handled with
BeanSerializer/BeanDeserializersupport use of@JsonUnwrappedwithout extra work. If you want it to work for custom types (ones with custom (de)serializers, you will need to do more work.Custom (de)serializers typically have to add explicit support for any and all annotations.
For serializers, you need to implement
JsonSerializer.unwrappingSerializer()and for deserializersJsonDeserializer.unwrappingDeserializer(): these are factory methods that create instances that will handle all the complexities of dealing with changing structure.I would not recommend extending
UnwrappingBeanSerializer; it is an internal class and unlikely to work for your case.