Is your feature request related to a problem? Please describe.
JacksonRecordConstructorExample.java.zip
I need to deserialize an object that has two constructors, a single-arg version that takes a java record, and a no-arg version. Depending on the provided JSON, I want Jackson to auto-detect which one to use.
Jackson never auto-detects the constructor that takes a record. Using @JsonCreator is not a good choice, because then it always uses the single-arg constructor instead of the no-arg.
Describe the solution you'd like
Jackson does auto-detect a version that has @JsonProperties for each parameter, like this:
public JacksonRecordConstructorExample( @JsonProperty("a") int a, @JsonProperty("b") int b, @JsonProperty("c") int c )
It seems to me that it ought to treat this constructor version equivalently and auto-detect it:
public record Triple( int a, int b, int c ) {}
public JacksonRecordConstructorExample( Triple triple )
Usage example
See attached test case
Additional context
I'm using Jackson 2.19.1
No response
Comment From: jessebarnum
btw your reporting feature does not allow uploads of .java files, which is why I had to zip my file
Comment From: pjfanning
Can you just put the code in a comment? Accepting zip files from strangers is a security risk. Or create a GitHub project and link it.
Comment From: jessebarnum
OK, here's the test case:
package com.prosc.workflows;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
/**
* Created by IntelliJ IDEA.
* User: jesse
* Date: 7/12/25
*/
public class JacksonRecordConstructorExample {
public record Triple( int a, int b, int c ) {}
private Triple triple;
// Test passes with this three-arg constructor
/*public JacksonRecordConstructorExample( @JsonProperty("a") int a, @JsonProperty("b") int b, @JsonProperty("c") int c ) {
System.out.println( "Three-arg constructor called" );
this.triple = new Triple( a, b, c );
}*/
//It seems like Jackson should treat this record constructor as equivalent to the three-arg version. Is there some annotation I can use to make Jackson treat it that way?
//@JsonCreator //This makes my test pass, but it's not a good solution because I have other cases where the no-arg constructor must be used
public JacksonRecordConstructorExample( Triple triple ) {
System.out.println( "One-arg constructor called" );
this.triple = triple;
}
public JacksonRecordConstructorExample() {
triple = null;
}
public static void main( String[] args ) throws Exception {
String jsonExample1 = """
{ "a": 1, "b": 2, "c": 3 }""";
final JacksonRecordConstructorExample example1 = new ObjectMapper().configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false ).readValue( jsonExample1, JacksonRecordConstructorExample.class );
Assertions.assertNotNull( example1.triple );
}
}
Comment From: JooHyukKim
Can you first try annotating the no-arg constructor also?
Jackson never auto-detects the constructor that takes a record. Using @JsonCreator is not a good choice, because then it always uses the single-arg constructor instead of the no-arg.
Btw, what is the intention behind your statement @jessebarnum ?
And this note also, regarding one-arg constructor might help.
Comment From: cowtowncoder
Due to all complexities and ambiguity involved, so-called "delegating" constructors are not auto-detected (your Record-taking Constructor is delegating one; as opposed to Properties-based one). You do need to use annotation, preferably:
@JsonCreator(mode = Mode.DELEGATING)
(omitting mode
MAY work... but there's fundamental ambiguity for 1-parameter case)
I don't think we ever will change this: based on all my experience, getting auto-detection actually work along all use cases, existing and future is... very difficult, brittle.
Comment From: jessebarnum
It seems like in the special case of a single-arg record, it could safely be used as if it were a property-based constructor with named parameters.
@JsonCreator won't work for me because I have multiple constructors used for different purposes. Here is a test case illustrating my problem:
package com.prosc.workflows;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
public class JacksonMultipleConstructorsExample {
public record Triple( int a, int b, int c ) {}
private int d, e;
private Triple triple;
//This constructor is needed for example2 to work
public JacksonMultipleConstructorsExample() {
System.out.println( "No-arg constructor called" );
}
//If I enable this constructor, it fixes example1, but example2 fails.
/*public JacksonMultipleConstructorsExample( @JsonProperty("a") int a, @JsonProperty("b") int b, @JsonProperty("c") int c ) {
System.out.println( "Three-arg constructor called" );
this.triple = new Triple( a, b, c );
}*/
//If I enable this annotation, it fixes example1, but example2 fails
// @JsonCreator
public JacksonMultipleConstructorsExample( Triple triple ) {
System.out.println( "One-arg constructor called" );
this.triple = triple;
}
public int getD() {
return d;
}
public void setD( int d ) {
this.d = d;
}
public int getE() {
return e;
}
public void setE( int e ) {
this.e = e;
}
public Triple getTriple() {
return triple;
}
public void setTriple( Triple triple ) {
this.triple = triple;
}
public static void main( String[] args ) throws Exception {
{
String jsonExample1 = """
{ "a": 1, "b": 2, "c": 3 }""";
final JacksonMultipleConstructorsExample example1 = new ObjectMapper().readValue( jsonExample1, JacksonMultipleConstructorsExample.class );
Assertions.assertNotNull( example1.triple );
}
{
String jsonExample2 = """
{
"triple": { "a": 1, "b": 2, "c": 3 },
"d": 4,
"e": 5
}
""";
final JacksonMultipleConstructorsExample example2 = new ObjectMapper().readValue( jsonExample2, JacksonMultipleConstructorsExample.class );
Assertions.assertNotNull( example2.triple );
Assertions.assertEquals( 4, example2.d );
}
}
}
Comment From: yawkat
I think jackson just doesn't do this form of structural detection. You would need to use the subtyping support.
Comment From: cowtowncoder
Ok, so: Jackson allows for at most:
- One properties-based constructor (and if none annotated, no-args taking one essentially works as on)
- One (full) annotated delegation-based constructor
One challenge with annotations is that once any are used for Creator-detection, no auto-detection will occur.
So if 2 is ok -- 1-arg delegation based; no-args for other cases -- you need to annotate both; using mode
.
Comment From: jessebarnum
Do you mean like this?
@JsonCreator( mode = JsonCreator.Mode.PROPERTIES )
public JacksonMultipleConstructorsExample() {
System.out.println( "No-arg constructor called" );
}
@JsonCreator( mode = JsonCreator.Mode.DELEGATING )
public JacksonMultipleConstructorsExample( Triple triple ) {
System.out.println( "One-arg constructor called" );
this.triple = triple;
}
When I try that, the delegating constructor works for this input:
{ "a": 1, "b": 2, "c": 3 }
But when I try with this input:
{
"triple": { "a": 1, "b": 2, "c": 3 },
"d": 4,
"e": 5
}
The no-arg constructor is not detected and I get Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "triple" (class com.prosc.workflows.JacksonMultipleConstructorsExample$Triple), not marked as ignorable (3 known properties: "a", "b", "c"]).
Comment From: cowtowncoder
@jessebarnum Almost -- but you are missing a setter or auto-detected field for delegating case? Basically something that does same as this:
@JsonCreator( mode = JsonCreator.Mode.PROPERTIES)
public JacksonMultipleConstructorsExample( Triple triple ) {
this.triple = triple;
}
but which is not possible since the signature is same as for delegating case (if I understand your intent correctly).
Although I think your case would be best solved if we implemented #5084 so you'd have:
@JsonCreator( mode = JsonCreator.Mode.DELEGATING_OR_PROPERTIES)
public JacksonMultipleConstructorsExample(@JsonProperty("triple") Triple triple ) {
this.triple = triple;
}
just wished I had time to work on that.
Comment From: JooHyukKim
Seems like I put a start to this one. https://github.com/FasterXML/jackson-databind/issues/5084#issuecomment-2798918716.
Shall we come up with draft solution in databind module as well, so we wont have to revert anything.
Comment From: jessebarnum
What I was expecting with this input:
{
"triple": { "a": 1, "b": 2, "c": 3 },
"d": 4,
"e": 5
}
was that it would use the no-arg constructor, and then call setTriple(), setD(), setE(). This is how it works if the Delegating constructor does not exist.
So essentially, what I'm asking for is, "If the constructor does not match, then try again as if that constructor did not exist"
Comment From: cowtowncoder
@jessebarnum Ok. I got confused with the example. This seems odd:
{
"triple": { "a": 1, "b": 2, "c": 3 },
"d": 4,
"e": 5
}
because structurally that seems off: a
/b
/c
somehow nested under "triple", compared to d
/e
.
There is no way to structurally transform things that way.
Or put another way: you have 2 classes (Triple
, JacksonRecordConstructorExample
) but trying to combine @JsonCreator
somehow combining/mixing the two. But Creators are always bound to declaring class -- so you cannot have Delegating creator for Triple
defined as constructor/static factory of JacksonRecordConstructorExample
.
You can have setTriple(Triple t)
of JacksonRecordConstructorExample
, of course. And @JsonCreator
of JacksonRecordConstructorExample
that takes "a", "b" and "c" to construct Triple
.
Not sure I am explaining this well, but I think I am finally understanding the actual problem.
Comment From: cowtowncoder
Seems like I put a start to this one. #5084 (comment).
Shall we come up with draft solution in databind module as well, so we wont have to revert anything.
While #5084 would be useful thing, realized that is not the problem we have here. FWTW.
Comment From: jessebarnum
I think there is still a communication mixup. You say "There is no way to structurally transform things that way," but that's not true - everything works (using the no-arg constructor, followed by setTriple, setD, setE), if the DELEGATING constructor does not exist.
If Jackson can't find a way to deserialize the class using the delegating constructor, can it fall back to checking other constructors to see if they work?
Comment From: cowtowncoder
@jessebarnum Ok I just do not understand intent then.
But one thing I did miss and should again warn -- part of FAQ. For test cases, PLEASE do not disable
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
because it very commonly hides specific issues. It's ok to use in production if defensive programming is desired, but for reproduction it is often problematic.
So it'd be good to see what issue -- if any -- surfaces when that feature is left on/enabled.