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

145 views
Skip to first unread message

Aravinda Baliga B

unread,
Jul 10, 2021, 12:18:14 PM7/10/21
to jackson-user
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.

Tatu Saloranta

unread,
Jul 11, 2021, 4:27:39 PM7/11/21
to jackson-user
Ok, so the problem here is, I think, that the second  call is for so-called "any getter":

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

which does not have a logical property name associated with it. I am guessing that "current name" is then just leftover from the previous
property. This sounds like a bug, I think -- while I am not 100% sure what value should be used, it should probably be cleared.
Maybe empty String would make most sense.
Similarly I wonder if any property name is associated with individual "any properties".

Would you be so kind as to file an issue against jackson-databind, outlining what you wrote above?
I think this is worth addressing for 2.13 or later versions.

-+ Tatu +-


--
You received this message because you are subscribed to the Google Groups "jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/b493e599-9ac4-43c2-a520-3d74729ea44dn%40googlegroups.com.

Aravinda Baliga B

unread,
Jul 11, 2021, 11:40:05 PM7/11/21
to jackson-user

Thanks a lot for the response. As mentioned I have created an issue against `Jackson-Databind`. Please find the link for the same: https://github.com/FasterXML/jackson-databind/issues/3206

I used following work around to fix in my application:
`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();
}
```
Reply all
Reply to author
Forward
0 new messages