Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Jackson recognize constructor field names as properties without explicit JsonProperty?

There is a simple class with parameterized constructor. I'm looking for a way to apply it for JSON deserialization with minimal annotations usage.

How to make Jackson recognize id field by its name without explicit JsonProperty?

Class:

public final class MyRec {

    final String id;

    public MyRec1(String id) {
        this.id = id;
    }
}

Deserialization fails unless annotation @JsonProperty("id") is present on the field.

Deserialization attempt:

new ObjectMapper().readValue("""{"id":"abc"}""", MyRec.class);

Exception:

Cannot construct instance of `sample.MyRec` (although at least one Creator exists): 
cannot deserialize from Object value (no delegate- or property-based Creator)
  at [Source: (String)"{"id":"abc"}"; line: 1, column: 2]

com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot construct instance of `sample.MyRec` (although at least one Creator exists):
  cannot deserialize from Object value (no delegate- or property-based Creator)
  at [Source: (String)"{"id":"abc"}"; line: 1, column: 2]
  at app//com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
like image 887
diziaq Avatar asked Oct 22 '25 08:10

diziaq


2 Answers

Parameter names module

To make Jackson capable of detecting the required constructor based on the number of properties without making use of data-binding annotations @JsonCreator and @JsonProperty you can add a dependency to Jackson Modules: Java 8 (a link to the Maven repository).

Jackson Modules: Java 8 is an umbrella multi-module which includes Parameter names module.

If you are using Spring Boot, the only thing needed to register the module is to place ParameterNamesModule as a Bean into the Spring's Context, and it would be grabbed at the application start-up and registred automatically by the JacksonAutoConfiguration while ObjectMapper would be configured.

As explained here you need to provide JsonCreator.Mode.PROPERTIES as argument while instantiating the module.

@Bean
public ParameterNamesModule parameterNamesModule() {
    return new ParameterNamesModule(JsonCreator.Mode.PROPERTIES);
}

If you're not using Spring in your project, then you would need to configure ObjectMapper manually:

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

Note

There are caveats. Specifically there's an issue with detecting a single-arg constructor. Here's a quote from the module-description:

Preconditions:

  • class Person must be compiled with Java 8 compliant compiler with option to store formal parameter names turned on (-parameters option). For more information about Java 8 API for accessing parameter names at runtime see this
  • if there are multiple visible constructors and there is no default constructor, @JsonCreator is required to select constructor for deserialization
  • if used with jackson-databind lower than 2.6.0, @JsonCreator is required. In practice, @JsonCreator is also often required with 2.6.0+ due to issues with constructor discovery that will be resolved in 2.7.
  • if class Person has a single argument constructor, its argument needs to be annotated with @JsonProperty("propertyName"). This is to preserve legacy behavior, see FasterXML/jackson-databind/#1498

So, to make the Jackson recognize a single-arg constructor, we need to apply @JsonProperty on the argument.

Here's also a mention of this issue JsonCreator.Mode.PROPERTIES documentation:

Mode that indicates that the argument(s) for creator are to be bound from matching properties of incoming Object value, using creator argument names (explicit or implicit) to match incoming Object properties to arguments.

Note that this mode is currently (2.5) always used for multiple-argument creators; the only ambiguous case is that of a single-argument creator.


This behavior might be fixed with Jackson 3 as mentioned here.

Java 16 Records

If you're using JDK 16+ in your project, then you can make this final class to be a record.

Records doesn't declare a default no-args constructor, instead the compiler generates a so-called canonical constructor, declaring all the arguments from the record's header (refer to the JLS 8.10.4. Record Constructor Declarations for more information).

And Jackson uses canonical constructor as a default creator to instantiate records.

The following record would be desirialized without issues:

public record MyRec(String id) {}
like image 131
Alexander Ivanchenko Avatar answered Oct 23 '25 23:10

Alexander Ivanchenko


Until Java 8, the names of method and constructor parameters were not present in compiled byte code. In Java 8 and later, the -parameters needs to be explicitly set to get the parameter names in byte code. If the name is not available in byte code, it is not available through reflection; you'd instead get arg0, arg1, etc.

Some frameworks have already been updated to use the parameter names, but a lot have not. As far as I know, Jackson belongs to the second group (most likely also to still support older Java versions). You can try to use the -parameters flag, but you probably still have to tell Jackson to use the constructor - either using @JsonProperty on the constructor parameter, or using @JsonCreator.

like image 41
Rob Spoor Avatar answered Oct 24 '25 00:10

Rob Spoor