Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson @JsonUnwrapped behaviour with custom JsonSerializer

Tags:

java

json

jackson

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.

like image 562
panfil Avatar asked Jun 15 '15 13:06

panfil


2 Answers

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();
        }
}
like image 155
Nikolay Rusev Avatar answered Nov 08 '22 21:11

Nikolay Rusev


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()));
    }   
}
like image 31
Michael Simons Avatar answered Nov 08 '22 20:11

Michael Simons