How does the default mode work for @JsonCreator?

1,584 views
Skip to first unread message

Psycho Punch

unread,
May 12, 2018, 4:28:23 PM5/12/18
to jackson-user
I understand that there are a few modes by which the ObjectMapper use single argument constructors to instantiate an object from JSON. However, I'd like to understand how the ObjectMapper chooses which mode it runs under by default, and how I can force it to use one mode for all JsonCreators.

Tatu Saloranta

unread,
May 13, 2018, 10:43:35 PM5/13/18
to jackson-user
Default mode to use (properties-based vs delegating creator) uses
heuristics, to try to figure out most likely intent,
in cases of single-argument creator (constructor or factory method,
annotated with `@JsonCreator`, but without:

1. `mode` property to determine which type to choose and
2. without explicit name for the one argument

if, so, heuristics is simply to try to see if there is an otherwise
detected property (inferred from field, setter and/or getter),
with name that matches implicit name of the argument. If so,
property-based creator approach is used.
Otherwise delegating.

So, for example:

public class BeanProps {
public int value;

@JsonCreator
public BeanProps(int value) {
this.value = value;
}
}

would be detected as properties-based if (and only if) something can
give the implicit name of constructor argument --
this means usually use of `jackson-module-parameter-names` (or
possibly paranamer module).

There are also some cases that might appear like use of default
logics, such as case of no `@JsonCreator` but
explicit name for parameter, but which are explicit resolved (in that
case, always as properties-based); or,
implicit single-argument constructor with String/int/long/boolean
argument, with no `@JsonCreator` or explicit
name (that case is always delegating).
In practice, I think the most common case of ambiguity and mismatch is
that of a single string argument. That can
very easily be used for either purpose (deserialize from String OR
match to the one property POJO has).

There is no way to change the default behavior, although I am thinking
that use of heuristics is quite fragile and in
many ways it would be better to perhaps not allow ambiguity at all.
The problem is, I think, that some users would really
really want to avoid any use of annotations, which is fundamentally
incompatible with the goal of no-surprises-always-explicit
requirement.

Does this help understand the logic?

-+ Tatu +-

Psycho Punch

unread,
May 14, 2018, 5:13:28 AM5/14/18
to jackson-user
Thank you very much for that response.

However, while the concept of default heuristics is a bit clearer to me now, the heuristics itself seems to be a little hard to predict, and, as you say fragile. I asked this question because I'm working on a code base that suddenly stopped working properly because of this fragility. The project uses Spring Boot, so I initially suspected the problem was caused by some underlying autoconfiguration. However, it turned out to be a problem with how the default mode works in Jackson as it's rather unpredictable.

In the code base, there are classes structured similarly to the following (ignore for now whether the domain is well represented):

public class abstract Book {

   
private Object content;

   
protected Book(String code, Object content) { ... }

   
public Object getContent() { ... }

}

public class FictionBook extends Book {

   
@JsonCreator
   
public FictionBook(Object content) {
       
super("fiction-code", content);
   
}

}

FictionBook class' constructor annotated with @JsonCreator has always worked under delegating mode. However, after adding dependency to Jackson's XML module, the code base started breaking as the constructor began running in properties mode. I'm just not sure what exactly changed as the classes in question remained untouched. What did the ObjectMapper find different to run the JsonCreator in a different mode in this case?


Tatu Saloranta

unread,
May 14, 2018, 10:40:02 AM5/14/18
to jackson-user
On Mon, May 14, 2018 at 2:13 AM, Psycho Punch <rdg...@gmail.com> wrote:
> Thank you very much for that response.
>
> However, while the concept of default heuristics is a bit clearer to me now,
> the heuristics itself seems to be a little hard to predict, and, as you say
> fragile. I asked this question because I'm working on a code base that
> suddenly stopped working properly because of this fragility. The project
> uses Spring Boot, so I initially suspected the problem was caused by some
> underlying autoconfiguration. However, it turned out to be a problem with
> how the default mode works in Jackson as it's rather unpredictable.

Yes, and this is why I am thinking of simply removing it.
The fix in your case is to add `mode` property.

But let's see....

> In the code base, there are classes structured similarly to the following
> (ignore for now whether the domain is well represented):
>
> public class abstract Book {
>
> private Object content;
>
> protected Book(String code, Object content) { ... }
>
> public Object getContent() { ... }
>
> }
>
> public class FictionBook extends Book {
>
> @JsonCreator
> public FictionBook(Object content) {
> super("fiction-code", content);
> }
>
> }
>
> FictionBook class' constructor annotated with @JsonCreator has always worked
> under delegating mode. However, after adding dependency to Jackson's XML
> module, the code base started breaking as the constructor began running in
> properties mode. I'm just not sure what exactly changed as the classes in
> question remained untouched. What did the ObjectMapper find different to run
> the JsonCreator in a different mode in this case?

This is highly speculative but perhaps Java 8 parameter names module
got added to the project?
That would bring implicit parameter name, "content", in, and in turn
link with field "content",
to indicate existence of property. That in turn would make heuristics
detect linkage.

Some projects use classpath auto-discovery, which I think is just
asking for trouble, but is fairly common
in Spring world for some reason. That could further cause mysterious
problems because now a transitive
dependency could pull in a new module; and new module's feature
(implicit parameter names) would have
effect of changing behavior.

Alternate hypothesis would be that compilation settings project use
have changed, and formerly parameter
names were not included in bytecode. But a change to settings could
have enabled inclusion, similarly causing
parameter name to be found and linked.

I hope this helps,

-+ Tatu +-

Psycho Punch

unread,
May 15, 2018, 4:40:33 AM5/15/18
to jackson-user
Tatu,

I really appreciate your responses to my question. Thank you very much.

I did a very quick investigation on our code base and found that in both setup, the ObjectMapper has ParameterNamesModule registered, so I don't think that's where the problem is. I'll continue monitoring this as much as I can and get back to you if I find something that might be of interest to other Jackson users. However, based on our conversation, we probably need this behavior locked down to make it more concrete so it doesn't introduce uncertainty in projects depending on Jackson. I'm not sure if I can help, but if you point me to where to start, I'll take a look.

Tatu Saloranta

unread,
May 15, 2018, 10:39:22 AM5/15/18
to jackson-user
What I was mostly suggesting was to simply change declarations as

@JsonCreator(mode=Mode.DELEGATING)

but alternatively you could consider custom sub-class of
`JacksonAnnotationIntrospector`, overriding:

public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?>
config, Annotated a);

to return this in case annotation is found, but with default mode
(Mode.DEFAULT or something).

I do not have solid plan for how detection should change for
jackson-databind itself, but any change to
logic will have to wait until 3.0. And further there are pretty badly
conflicting wishes from users as to
how things should work, divided into 2 main themes:

1. "I don't want to use any Jackson annotations, ever, it should just
work the way I use it" (which may be
"always properties", or might be not -- these are personal
preferences in the end)
2. "I want logic to be fully predictable and stable, to minimize
change of things breaking".

I think both are reasonable wishes, but unfortunately they are in
direct conflict when it comes to finding an
implementation.

One possible route might be to expose yet another abstraction like
`CreatorResolver` (or such), which would be called
to resolve ambiguous cases.

Finally, all of this probably has to wait until property introspection
rewrite for 3.0, wherein detection starts with creator
detection, completing fully before considering other property
accessors. This is needed to fully unify logic and annotation
access and solve other unresolved issues. But changes included will
affect handing of properties-vs-delegating case too.

-+ Tatu +-
Reply all
Reply to author
Forward
0 new messages