Search code examples
javajsonjacksonjackson-databindjackson2

Jackson Custom Serializer shows the same context for 2 different field during the Json Serialization


I am trying to create a JSON based on my Object class POJO. For some fields, I would like to use the CustomSerializer as I would like to create the fields according to my requirement. Hence, I have created the CustomSerializer.class.

The CustomSerializer will be called by 2 different fields in my POJO and I would like to handle the things differently based on which field is making the call. For one of the fields (extensions) I would like to have the fieldName and for other field (withoutExtensions) I do not wish to have the fieldname in my JSON.

The problem I am facing is that when CustomSerializer is called then I am getting the same fieldname for both the calls due to which I am unable to make a differentiation which field is currently calling the CustomSerializer.

Following code samples will provide more clarity on the issue I am facing:

Customer POJO class used for serializing the JSON:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@NoArgsConstructor
public class Customer {
    private String isA;
    private String name;

    @JsonSerialize(using = CustomSerializer.class)
    private Map<String, Object> extensions = new HashMap<>();

    private Map<String, Object> withoutExtensions = new HashMap<>();

    @JsonAnyGetter
    @JsonSerialize(using = CustomSerializer.class)
    public Map<String, Object> getWithoutExtensions() {
        return withoutExtensions;
    }

}

Following is my CustomSerializer which will be called by 2 fields (extensions and withoutExtensions) during the creation of JSON:

public class CustomSerializer extends JsonSerializer<Map<String, Object>> {

    @Override
    public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
        //I would like to create the outer object for "Extensions" but do not want to create outer object for "WithoutExtensions"
        
         System.out.println(gen.getOutputContext().getCurrentName());

        //In my case for both "Extensions" and "WithoutExtensions" i get the "currentName" as "Extensions" how can I ensure which field is calling this sealizer at
        // present
    }
}

Following is my Main class which will create a JSON:

public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        final Customer customer = new Customer();

        customer.setName("Jackson");

        Map<String, Object> extensions = new HashMap<>();
        extensions.put("WithObject", "With");
        customer.setExtensions(extensions);

        Map<String, Object> withoutExtensions = new HashMap<>();
        extensions.put("WithoutObject", "Without");
        customer.setWithoutExtensions(withoutExtensions);

        final String eventAsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
        System.out.println(eventAsJson);
    }
}

As we can see when I run the application the CustomSerializer would print extensions in both cases. I believe it should print extensions only once and in the next case either it should provide withoutExtensions or empty string.

I just wanted to know if this an bug on the Jackson part or is there any work-around that I can try to differentiate which field is making a call to my CustomSerializer.

Any help would be really appreciated. Thanks.


Solution

  • A. Create two Map serialisers where one creates outer object and another not

    Pros:

    • Easy to implement
    • Easy to test
    • One class does exactly one thing
    • Map serialiser which does not create outer object could be replaced by custom Map serialiser (if possible)

    Cons:

    • Could be problematic if they need to share state.
    • Possibly duplicated code

    B. Implement ContextualSerializer interface

    Pros:

    • Can be configured for every field separately
    • Can share state if needed. User control how many instances are created.

    Cons:

    • Does more than 1 thing
    • Can be easily over complicated

    Examples: