1

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.

BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98

2 Answers2

1

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:

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 1
    Thanks a lot for the response. This really helped me and I choose option `B` as I felt that would be easiest to reduce the redundant code. I accept this answer and thanks again. Have a great day :) – BATMAN_2008 Jul 10 '21 at 12:08
1

Based on the response from @Michal I modified the code and it worked for both the scenario. Posting the complete code sample as it can be helpful to someone in the future:

Customer.class added the @Extensions on required fields:

@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)
    @Extensions(extension = "extensions")
    private Map<String, Object> extensions = new HashMap<>();

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

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

}

CustomSerializer:

@NoArgsConstructor
public class CustomSerializer extends JsonSerializer<Map<String, Object>> implements ContextualSerializer {

    private String context = "";

    public CustomSerializer(String context) {
        this.context = context;
    }


    @Override
    public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
        if (this.context.equals("extensions")) {
            System.out.println("Extensions : " + this.context);
        } else if (this.context.equals("withoutExtensions")) {
            System.out.println("Without Extensions : " + this.context);
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        Extensions extensions = beanProperty.getAnnotation(Extensions.class);
        if (extensions != null) {
            return new CustomSerializer(extensions.extension());
        }
        return this;
    }
}


@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Extensions {
    String extension();
}
BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98