javajava-8jacksonjackson-databindjackson-modules

Jackson ParameterNamesModule


I found this method in the code:

private MessageConverter makeJsonConverter() {
    var jsonConverter = new MappingJackson2MessageConverter();
    var mapper = jsonConverter.getObjectMapper();
    mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
    return jsonConverter;
}

Can anyone explain me the meaning of mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES)) ? How do we change the behaviour of mapping here? What will be different if we compare it with the default ObjectMapper?


Solution

  • The best and up to date answer for this question you can find on the GitHub page for this module: jackson-modules-java8/parameter-names.

    Main goal:

    Jackson module that adds support for accessing parameter names; a feature added in JDK 8.

    Delegating creator:

    By default, Jackson uses single argument constructors for mapping whole JSON value into value of the argument.

    To override this behaviour use the ParameterNamesModule constructor with JsonCreator.Mode parameter. For example, to use same behaviour as for constructors with multiple parameters:

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
    

    To understand it a little bit better we need to create a simple POJO and log what is invoked when. Take a look at below class:

    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
    import lombok.ToString;
    
    import java.util.Map;
    import java.util.Objects;
    
    public class JsonCreatorApp {
        public static void main(String[] args) throws JsonProcessingException {
            JsonMapper mapper = JsonMapper.builder()
                    // change the mode value and run
                    .addModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))
                    .build();
    
            String json = "{\"field1\": \"val\",\"field2\":314}";
            Fields fields = mapper.readValue(json, Fields.class);
            System.out.println(fields);
        }
    }
    
    @ToString
    class Fields {
    
        private String field1;
        private int field2;
    
        public Fields() {
            System.out.println("no-arg constructor");
        }
    
        @JsonCreator
        public Fields(@JsonProperty("field1") String field1, @JsonProperty("field2") int field2) {
            System.out.println("2-args constructor: (" + field1 + ", " + field2 + ")");
    
            this.field1 = field1;
            this.field2 = field2;
        }
    
        public String getField1() {
            return field1;
        }
    
        public void setField1(String field1) {
            System.out.println("Set Field1: " + field1);
            this.field1 = field1;
        }
    
        public int getField2() {
            return field2;
        }
    
        public void setField2(int field2) {
            System.out.println("Set Field2: " + field2);
    
            this.field2 = field2;
        }
    }
    

    For different modes app prints different results.
    JsonCreator.Mode.DEFAULT:

    2-args constructor: (val, 314)
    Fields(field1=val, field2=314)
    

    JsonCreator.Mode.DISABLED:

    no-arg constructor
    Set Field1: val
    Set Field2: 314
    Fields(field1=val, field2=314)
    

    JsonCreator.Mode.DELEGATING:

    Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.celoxity.Fields`: More than one argument (#0 and #1) left as delegating for Creator [constructor for `com.celoxity.Fields` (2 args), annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}: only one allowed
     at [Source: (String)"{"field1": "val","field2":314}"; line: 1, column: 1]
    

    JsonCreator.Mode.PROPERTIES:

    2-args constructor: (val, 314)
    Fields(field1=val, field2=314)
    

    Now, let's add extra method to our POJO and change mode for 2-arg constructor to JsonCreator.Mode.PROPERTIES to avoid problems:

    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    private static Fields of(Map<String, Object> map) {
        System.out.println("of: (" + map + ")");
        String field1 = Objects.toString(map.get("field1"), null);
        int field2 = Integer.parseInt(Objects.toString(map.get("field2"), "0"));
    
        return new Fields(field1, field2);
    }
    

    Now it works for JsonCreator.Mode.DELEGATING:

    of: ({field1=val, field2=314})
    2-args constructor: (val, 314)
    Fields(field1=val, field2=314)
    

    You can play with this example and try out different configurations. Add more constructors, remove some, remove JsonCreator annotation from constructor and of method and spot the difference.