I want to create a custom serializer which does a tiny bit of work and then leaves the rest for default serialization.
For example:
#JsonSerialize(using = MyClassSerializer.class)
public class MyClass {
...
}
public class MyClassSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass myClass, JsonGenerator generator,
SerializerProvider provider)
throws JsonGenerationException, IOException {
if (myClass.getSomeProperty() == someCalculationResult) {
provider.setAttribute("special", true);
}
generator.writeObject(myClass);
}
}
With the idea of creating other custom serializers for aggregated objects which behave differently based on the 'special' attribute value. However, the above code does not work, as it unsurprisingly goes into an infinite recursion.
Is there a way to tell jackson to use default serialization once I have set the attribute? I don't really want enumerate all the properties like many custom serializers as the class is fairly complex and I don't want to have to do dual maintenance with the serializer every time I change the class.
A BeanSerializerModifier will provide you access to the default serialization.
Inject a default serializer into the custom serializer
public class MyClassSerializer extends JsonSerializer<MyClass> {
private final JsonSerializer<Object> defaultSerializer;
public MyClassSerializer(JsonSerializer<Object> defaultSerializer) {
this.defaultSerializer = checkNotNull(defaultSerializer);
}
#Override
public void serialize(MyClass myclass, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (myclass.getSomeProperty() == true) {
provider.setAttribute("special", true);
}
defaultSerializer.serialize(myclass, gen, provider);
}
}
Create a BeanSerializerModifier for MyClass
public class MyClassSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (beanDesc.getBeanClass() == MySpecificClass.class) {
return new MyClassSerializer((JsonSerializer<Object>) serializer);
}
return serializer;
}
}
Register the serializer modifier
ObjectMapper om = new ObjectMapper()
.registerModule(new SimpleModule()
.setSerializerModifier(new MyClassSerializerModifier()));
#JsonSerialize(using = MyClassSerializer.class)
public class MyClass {
...
}
public class MyClassSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass myClass, JsonGenerator generator,
SerializerProvider provider)
throws JsonGenerationException, IOException {
if (myClass.getSomeProperty() == someCalculationResult) {
provider.setAttribute("special", true);
} else {
provider.defaultSerializeValue(myClass, generator);
}
}
}
if you are just writing an object as normal use the above
You can use #JsonGetter instead of using a custom serializer if that's the only change you want to make.
public class MyClass{
#JsonGetter("special")
protected boolean getSpecialForJackson() {
return myClass.getSomeProperty() == someCalculationResult;
}
}
To add to the chosen answer, the serializer implementation may also have to implement ContextualSerializer and ResolvableSerializer interfaces. Please take a look at a related issue here
https://github.com/FasterXML/jackson-dataformat-xml/issues/259
public class MyClassSerializer extends JsonSerializer<MyClass>
implements ContextualSerializer, ResolvableSerializer {
private final JsonSerializer<Object> defaultSerializer;
public MyClassSerializer(JsonSerializer<Object> defaultSerializer) {
this.defaultSerializer = checkNotNull(defaultSerializer);
}
#Override
public void serialize(MyClass myclass, JsonGenerator gen, SerializerProvider provider)
throws IOException {
if (myclass.getSomeProperty() == true) {
provider.setAttribute("special", true);
}
defaultSerializer.serialize(myclass, gen, provider);
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
if (defaultSerializer instanceof ContextualSerializer) {
JsonSerializer<?> contextual = ((ContextualSerializer)defaultSerializer).createContextual(prov, property);
return new MyClassSerializer((JsonSerializer<Object>)contextual);
}
return new MyClassSerializer(defaultSerializer);
}
#Override
public void resolve(SerializerProvider provider) throws JsonMappingException {
if (defaultSerializer instanceof ResolvableSerializer) {
((ResolvableSerializer)defaultSerializer).resolve(provider);
}
}
}
Related
I am not able to get JsonDeserializer to process empty values. I have a DAO and a custom deserialiser like this.
Class Example {
#JsonDeserialize(using = ResetableValueDeserializer.class)
Resetable<String> property;
}
class ResetableValueDeserializer extends StdDeserializer<ResetableValue<String>> {
ResetableValueDeserializer() {
super(ResetableValue.class);
}
protected ResetableValueDeserializer(Class<?> vc) {
super(vc);
}
#Override
public ResetableValue<String> deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JacksonException {
JsonNode node = jp.readValueAsTree();
if (node.asText().isEmpty()) {
return null;
}
return new ResetableValue<>(node.asText());
}
#Override
public ResetableValue<String> getNullValue(DeserializationContext ctxt) throws JsonMappingException {
return ResetableValue.asReset();
}
#Override
public ResetableValue<String> getAbsentValue(DeserializationContext ctxt) {
return ResetableValue.asNotProvided();
}
But neither the deserialise() nor getAbsentValue() is triggered when I have an empty input JSON like this {}. While the getNull() method is handling input JSON {property: null}
I am trying to create a standard way to serialize and deserialize for Enum on Jackson.
My serialize is easy:
public class EnumSerializer extends JsonSerializer<Enum<?>> {
#Override
public void serialize(Enum<?> data, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (data == null) {
gen.writeString("");
} else {
gen.writeString(data.name());
}
}
}
Now I am working on Deserializer:
public class EnumDeserializer extends JsonDeserializer<Enum<?>> {
#Override
public Enum<?> deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext)
throws IOException, JsonProcessingException {
String dataStr = jsonparser.getText();
if (dataStr == null || dataStr.isEmpty()) {
return null;
} else {
Class<Enum<?>> enumClass = null; // How can I get enumClass?
for(Enum<?> one: enumClass.getEnumConstants()){
if(one.name().equals(dataStr)){
return one;
}
}
return null;
}
}
}
But you can see I have trouble to get enumClass.
Could you please help me?
Thanks!
If you really want to create the custom EnumDeserializer you can see the implementation of Jackson:
com.fasterxml.jackson.databind.deser.std.EnumDeserializer
But as I can see you try to implement the standard behavior of Jackson.
I wanted to test my serializer which parses my java object to a json object. This is my Serializer class:
public class CountryCodeSerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (value == null) {
generator.writeString("{}");
} else {
generator.writeString(value.toString());
}
}
}
My test looks like this:
#Before
public void setUp() throws Exception {
stringJson = new StringWriter();
generator = new JsonFactory().createGenerator(stringJson);
provider = new ObjectMapper().getSerializerProvider();
countryCode = CountryCode.parse("us");
}
#Test
public void parsingNullReturnsNull() throws Exception {
assertThat(countryCodeSerializer.serialize(countryCode, generator, provider)).isEqualTo("{'countrycode':'us'}); //this doesn't work, since serialize() is void
//countryCodeSerializer.serialize(countryCode, generator, provider); //this throws an java.lang.NullPointerException
}
So how can I test my serializer? I tried other answers to similar questions, but nothing worked for me.
I use the serializer like this in my other clases:
#JsonSerialize(using = CountryCodeSerializer.class)
private CountryCode countryCode;
Ok thank you for your answers. I got it now this way and it works fine:
I changed my serializer a little bit:
public class CountryCodeSerializer extends JsonSerializer<CountryCode> {
#Override
public void serialize(CountryCode value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
if (null == value) {
throw new IllegalArgumentException("CountryCode is null");
} else {
generator.writeString(value.toString());
}
}
}
And here are my two tests:
public class CountryCodeSerializerTest {
private CountryCodeSerializer countryCodeSerializer;
private JsonGenerator jsonGenerator;
#Before
public void setUp() throws Exception {
countryCodeSerializer = new CountryCodeSerializer();
jsonGenerator = mock(JsonGenerator.class);
}
#Test
public void testNullCountryCodeThrowsIllegalArgumentException() throws Exception {
try {
countryCodeSerializer.serialize(null, jsonGenerator, null);
fail("An IllegalArgumentException should have been thrown.");
} catch (IllegalArgumentException e) {
//ok
}
}
#Test
public void testCountryCodeConvertedToJsonString() throws Exception {
countryCodeSerializer.serialize(CountryCode.parse("us"), jsonGenerator, null);
verify(jsonGenerator).writeString("us");
}
}
Something like this:
#Mock
private JsonGenerator generator;
#Test
public void testInstanceWithValue() {
//SETUP
String expectedValue = "test value";
CountryCode value = mock(CountryCode.class);
when(value.toString()).thenReturn(expectedValue);
// CALL
CountryCodeSerializer instance = new CountryCodeSerializer(value, generator, null);
// VERIFY
verify(generator).writeString(expectedValue);
}
#Test
public void testInstanceWithNull() {
//SETUP
CountryCode value = null;
// CALL
CountryCodeSerializer instance = new CountryCodeSerializer(value, generator, null);
// VERIFY
verify(generator).writeString("{}");
}
This can be achieved by creating a custom JsonGenerator that stores what is written to it.
class TestJsonGenerator extends JsonGenerator {
private StringBuilder stringBuilder = new StringBuilder();
...
#Override
public void writeString(String text) {
stringBuilder.append(text);
}
public String getText() {
return stringBuilder.toString();
}
}
Then you verify the generated content, without needing to check all the calls to writeString that were made:
TestJsonGenerator testGenerator = new TestJsonGenerator();
serializer.serialize(countryCode, testGenerator, provider);
assertThat(testGenerator.getText()).isEqualsTo("{ \"foo\": \"bar\" }");
I have a situation where I need to customize the serialization/deserialization of some JSON. I have simplified this into a readable example. I have a Container class that holds objects implementing MyInterface. In my example ClassA, ClassB, IntegerHolder and StringHolder implement the interface. By adding the #JsonTypeInfo annotation to my interface (and container):
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
and registering types names for each class, I can successfully read/write these to/from this JSON:
{"type":"Container","items":
[ {"type":"classA","aValue":"AAA"},
{"type":"classB","bValue":"BBB"},
{"type":"intHolder","value":123},
{"type":"stringHolder","value":"abc"} ] }
That is all very nice :) My problem is that I want to customize the serialization of the intHolder and stringHolder because they are just wrappers around native types. My JSON will be frequently edited by hand and the primitive types will be used a LOT. So I want to simplify the JSON to:
{"type":"Container","items":
[ {"type":"classA","aValue":"AAA"},
{"type":"classB","bValue":"BBB"},
123,
"abc" ] }
I have written a Serializer and Deserializer (extending StdSeralizer and StdDeserializer), put them in a SimpleModule and registered it with the mapper (as illustrated here on SO) and in isolation, it works well. By that, I mean that I can serialize/deserialize the IntegerHolder and StringHolder if they are the only objects in the container, and then only if I remove the #JsonTypeInfo annotation from the interface. If I do not, then I get this failure while writing to JSON:
[main] ERROR MyTests - can't write the Container
com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type MyInterface (by serializer of type MyTests$MyInterfaceSerializer) (through reference chain: Container["items"])
at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1047)
at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:142)
at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serializeTypedContents(ObjectArraySerializer.java:316)
at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serializeContents(ObjectArraySerializer.java:217)
at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serialize(ObjectArraySerializer.java:201)
at com.fasterxml.jackson.databind.ser.std.ObjectArraySerializer.serialize(ObjectArraySerializer.java:25)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:575)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeWithType(BeanSerializerBase.java:552)
at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:129)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3387)
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:2747)
at MyTests.testItemSerializationDeserializationEquality(MyTests.java:51)
at MyTests.testSerialization(MyTests.java:41)
But of course, with the #JsonTypeInfo removed, Jackson doesn't know how to deserialize ClassA and ClassB...so that fails while reading the JSON with:
[main] INFO MyTests - {"type":"Container","items":[{"aValue":"AAA"},{"bValue":"BBB"},123,"abc"]}
[main] ERROR MyTests - can't read the Container
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of MyInterface, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
at [Source: java.io.ByteArrayInputStream#37883b97; line: 1, column: 45] (through reference chain: Container["items"]->Object[][0])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:857)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:139)
at MyTests$MyInterfaceDeserializer.deserialize(MyTests.java:163)
at MyTests$MyInterfaceDeserializer.deserialize(MyTests.java:139)
I feel like Jackson can do it and I'm close to getting Jackson configured to serialize/deserialize both sets of classes, but so far my attempts have not been fruitful.
Any pointers to get me going in the right direction would be most appreciated...thanks in advance!
Here are the 7 classes in my test example:
MyInterface.java
import com.fasterxml.jackson.annotation.*;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface MyInterface
{
}
Container.java
import com.fasterxml.jackson.annotation.*;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public class Container
{
public Container()
{
}
public Container(MyInterface... items)
{
this.items = items;
}
public MyInterface[] getItems()
{
return items;
}
public void setItems(MyInterface[] items)
{
this.items = items;
}
#Override
public boolean equals(Object obj)
{
for (int i = 0; i < items.length; i++)
if (!(items[i].equals(((Container)obj).items[i])))
return false;
return true;
}
private MyInterface[] items;
}
MyTests.java
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.std.*;
import com.fasterxml.jackson.databind.jsontype.*;
import com.fasterxml.jackson.databind.module.*;
import com.fasterxml.jackson.databind.node.*;
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.ser.std.*;
import org.junit.*;
import org.slf4j.*;
import java.io.*;
public class MyTests
{
#Test
public void testSerialization()
{
ClassA a = new ClassA();
a.setaValue("AAA");
ClassB b = new ClassB();
b.setbValue("BBB");
IntegerHolderClass int_holder = new IntegerHolderClass();
int_holder.setValue(123);
StringHolderClass string_holder = new StringHolderClass();
string_holder.setValue("abc");
// Testing with ONLY the non-customized classes works fine with the #JsonTypeInfo annotation on MyInterface
// if the custom de/serializers are not registered via the module
// testItemSerializationDeserializationEquality(new Container(a, b), Container.class);
// Testing with ONLY the customized classes works fine with the custom de/serializers registered via the module
// if the #JsonTypeInfo annotation on MyInterface is removed
// testItemSerializationDeserializationEquality(new Container(int_holder, string_holder), Container.class);
// This variation tests them all together...doesn't work under either scenario
testItemSerializationDeserializationEquality(new Container(a, b, int_holder, string_holder), Container.class);
}
private void testItemSerializationDeserializationEquality(Object original, Class expected_super_type)
{
ObjectMapper mapper = createMapper();
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
try
{
mapper.writeValue(outstream, original);
outstream.flush();
}
catch (IOException e)
{
LOG.error("can't write the " + original.getClass().getSimpleName(), e);
}
LOG.info(outstream.toString());
Object copy = null;
try
{
copy = mapper.readValue(new ByteArrayInputStream(outstream.toByteArray()), expected_super_type);
}
catch (Exception e)
{
LOG.error("can't read the " + original.getClass().getSimpleName(), e);
}
Assert.assertNotNull(copy);
Assert.assertTrue(copy.equals(original));
}
private ObjectMapper createMapper()
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(new NamedType(ClassA.class, "classA"));
mapper.registerSubtypes(new NamedType(ClassB.class, "classB"));
mapper.registerSubtypes(new NamedType(IntegerHolderClass.class, "intHolder"));
mapper.registerSubtypes(new NamedType(StringHolderClass.class, "stringHolder"));
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier()
{
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
{
if (MyInterface.class.isAssignableFrom(beanDesc.getBeanClass()))
return new MyInterfaceDeserializer(deserializer);
return deserializer;
}
});
module.setSerializerModifier(new BeanSerializerModifier()
{
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer)
{
if (MyInterface.class.isAssignableFrom(beanDesc.getBeanClass()))
return new MyInterfaceSerializer(serializer);
return serializer;
}
});
mapper.registerModule(module);
return mapper;
}
static class MyInterfaceSerializer extends StdSerializer<MyInterface> implements ResolvableSerializer
{
public MyInterfaceSerializer(JsonSerializer<?> def)
{
super(MyInterface.class);
_default = (JsonSerializer<MyInterface>) def;
}
#Override
public void serialize(MyInterface value, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
if (value instanceof IntegerHolderClass)
jgen.writeNumber(((IntegerHolderClass) value).getValue());
else if (value instanceof StringHolderClass)
jgen.writeString(((StringHolderClass) value).getValue());
else
_default.serialize(value, jgen, provider);
}
#Override
public void resolve(SerializerProvider provider) throws JsonMappingException
{
}
private final JsonSerializer<MyInterface> _default;
}
static class MyInterfaceDeserializer extends StdDeserializer<MyInterface> implements ResolvableDeserializer
{
public MyInterfaceDeserializer(JsonDeserializer<?> def)
{
super(MyInterface.class);
_default = def;
}
#Override
public MyInterface deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
TreeNode node = parser.getCodec().readTree(parser);
if (node instanceof TextNode)
{
StringHolderClass holder = new StringHolderClass();
holder.setValue(((TextNode) node).textValue());
return holder;
}
else if (node instanceof IntNode)
{
IntegerHolderClass holder = new IntegerHolderClass();
holder.setValue(((IntNode) node).intValue());
return holder;
}
return (MyInterface) _default.deserialize(parser, context);
}
#Override
public void resolve(DeserializationContext context) throws JsonMappingException
{
// ((ResolvableDeserializer)_default).resolve(context);
}
private final JsonDeserializer<?> _default;
}
final static Logger LOG = LoggerFactory.getLogger(MyTests.class);
}
ClassA.java
public class ClassA implements MyInterface
{
public String getaValue()
{
return _aValue;
}
public void setaValue(String aValue)
{
_aValue = aValue;
}
#Override
public boolean equals(Object obj)
{
return obj instanceof ClassA && _aValue.equals(((ClassA)obj)._aValue);
}
private String _aValue;
}
ClassB.java
public class ClassB implements MyInterface
{
public String getbValue()
{
return _bValue;
}
public void setbValue(String bValue)
{
_bValue = bValue;
}
#Override
public boolean equals(Object obj)
{
return obj instanceof ClassB && _bValue.equals(((ClassB)obj)._bValue);
}
private String _bValue;
}
StringHolderClass.java
public class StringHolderClass implements MyInterface
{
public String getValue()
{
return _value;
}
public void setValue(String value)
{
_value = value;
}
#Override
public boolean equals(Object obj)
{
return obj instanceof StringHolderClass && _value.equals(((StringHolderClass)obj)._value);
}
private String _value;
}
IntegerHolderClass.java
public class IntegerHolderClass implements MyInterface
{
public int getValue()
{
return _value;
}
public void setValue(int value)
{
_value = value;
}
#Override
public boolean equals(Object obj)
{
return obj instanceof IntegerHolderClass && _value.equals(((IntegerHolderClass)obj)._value);
}
private Integer _value;
}
Two options:
Custom deserializer for MyInterface and then you do not need the JsonTypeInfo - all the logic will be in the deserializer.
You can try and have IntegerHolder and StringHolder implement another interface let's say Holder and change the JsonTypeInfo annotation to:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl=Holder.class)
And for Holder.class specify a deserializer.
I have two classes like this:
public class A {
String aProp = "aProp";
public String getAProp() {
return aProp;
}
}
public class B {
String bProp = "bProp";
A a = new A();
#JsonProperty("bProp")
public String getBProp() {
return bProp;
}
#JsonSerialize(using = CustomSerializer.class)
public A getA() {
return a;
}
}
I'm expecting to get JSON like this:
{
"bProp": "bProp", // just serizlised bProp
"sProp1": "sProp1_aProp", // computed using aProp
"sProp2": "sProp2_aProp" // computed another way
}
So I wrote custom JsonSerializer like this:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class CustomSerializer extends JsonSerializer<A> {
#Override
public void serialize(A a, JsonGenerator json, SerializerProvider provider) throws IOException {
json.writeStringField("sProp1", "sProp1_" + a.getAProp());
json.writeStringField("sProp2", "sProp2_" + a.getAProp());
}
}
But I keep getting an error:
com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value
Unless I put json.writeStartObject(); and json.writeEndObject(); in serialize method (so it produces wrong JSON).
So I'm looking for a solution like #JsonUnwrapped to use with custom JsonSerializer.
I understand your problem and the thing that you need is UnwrappingBeanSerializer. You can see another related SO post:
Different JSON output when using custom json serializer in Spring Data Rest
The problem is that you cannot have both annotations #JacksonUnwrapped and #JsonSerialize in one field because when you have #JsonSerializer Jackson will always write field name.
Here is the complete solution:
public class CustomSerializer extends UnwrappingBeanSerializer {
public CustomSerializer(BeanSerializerBase src, NameTransformer transformer) {
super(src, transformer);
}
#Override
public JsonSerializer<Object> unwrappingSerializer(NameTransformer transformer) {
return new CustomSerializer(this, transformer);
}
#Override
protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
A a = (A) bean;
jgen.writeStringField("custom", a.getAProp());
jgen.writeStringField("custom3", a.getAProp());
}
#Override
public boolean isUnwrappingSerializer() {
return true;
}
}
Test case, you should redefine your object mapper with custom configuration or research for other method .
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = Application.class)
public class ColorsTest {
ObjectMapper mapper = new ObjectMapper();
#Before
public void setUp(){
mapper.registerModule(new Module() {
#Override
public String getModuleName() {
return "my.module";
}
#Override
public Version version() {
return Version.unknownVersion();
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
if(beanDesc.getBeanClass().equals(A.class)) {
return new CustomSerializer((BeanSerializerBase) serializer, NameTransformer.NOP);
}
return serializer;
}
});
}
});
}
#Test
public void testSerializer() throws JsonProcessingException {
System.out.println(mapper.writeValueAsString(new B()));
}
}
Class B:
public class B {
#JsonProperty("bProp")
public String getBProp() {
return "bProp";
}
#JsonUnwrapped
public A getA() {
return new A();
}
}
I like to add this post and solution to the question asked here: Using custom Serializers with JsonUnwrapperd as the original poster is using JsonSerializer as I am. The suggest approach with the UnwrappingBeanSerializer won't work in this case. My post has a slightly different goal, but the idea from the post should be applicable to your use case easily, as it is just overwriting one more method and not having to add bunch of stuff apart from JsonUnwrapped on the property.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
public class Test {
static class A {
String aProp = "aProp";
public String getAProp() {
return aProp;
}
}
static class B {
String bProp = "bProp";
A a = new A();
#JsonProperty("bProp")
public String getBProp() {
return bProp;
}
#JsonSerialize(using = CustomSerializer.class)
#JsonUnwrapped
public A getA() {
return a;
}
}
static class CustomSerializer extends JsonSerializer<A> {
#Override
public boolean isUnwrappingSerializer() {
return true;
}
#Override
public void serialize(A a, JsonGenerator json, SerializerProvider provider) throws IOException {
json.writeStringField("sProp1", "sProp1_" + a.getAProp());
json.writeStringField("sProp2", "sProp2_" + a.getAProp());
}
}
public static void main(String... a) throws Exception {
final ObjectMapper o = new ObjectMapper();
o.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(o.writeValueAsString(new B()));
}
}