On removing need for @JsonCreator for constructors (related to Java8, issue #399)

5,345 views
Skip to first unread message

Tatu Saloranta

unread,
Oct 2, 2014, 6:37:18 PM10/2/14
to jacks...@googlegroups.com
(note: this has been discussed on and off for a while -- just want to re-kindle discussion now that underlying bugs have been resolved).

With Jackson, many things are auto-detected, based on old Java Beans specification, and some common-sense extensions. Public fields, public getters, and setters of any visibility level may be auto-detected to imply existence of logical properties, and access to reading/setting value of that property.
One class of accessors, however, so-called creators (constructors and in-class factory methods) has been left mostly without auto-detection; mostly because before Java 8, there has not been a standard way to figure out names of parameters.

Because of this, (almost *) all Creator methods have needed two things:

(1) Explicit annotation to provide for name of parameter
(2) @JsonCreator annotation to indicate actual creator method itself

There have been some relaxations; for example, (2) is actually not strictly required if (1) is satisfied. Also, as alternative for (1), Injectable values are accepted as well; these define name to map as well.

But with Java8 this changes; and there are also alternative means (paranamer library, other JVM languages that add metadata) to provide names. This means that we could actually loosen (1) to just mean that "all parameters must have names; either implied ones (with Java8/Paranamer etc), or explicit annotated ones".

However there are two potential problems with this approach:

(1) Currently following creators behave differently:
  (a)  @JsonCreator
        public Value(String a) { ... }

   (b)  @JsonCreator
         public Value(JsonProperty("a") String a)  { ... }

   where (a) expects JSON String to match, and (b) expects JSON Object like { "a": "value" }

  But if there is no difference to tell the two apart (since all parameters now suddenly have a name), how to tell delegating case (a) from property-based one (b)?

(2) Just because a Constructor exists and is visible (even public) does not mean it was meant to be used as Creator. Given that Jackson has not used these automatically so far, there has been no risk of accidental usage. Similarly, parameter names may be arbitrary and may not match excepted external names

So: simply starting to auto-detect constructors as Creators could be a problem, if made in absence of explicit hint.
(note that I omit question of factory methods here -- for now, I think we will assume that factory methods will still require use of explicit @JsonCreator)

There are multiple approaches we could take to allow auto-detection of these constructors.
But one combination that could perhaps work well would be to

1. Add a new MapperFeature, like AUTO_DETECT_CONSTRUCTORS, with setting of 'true'; disabling of which would prevent automatic detection
2. Consider the fact that core jackson-databind will NOT be able to detect method/constructor parameter names, so that the only way to really make auto-detection work is to actually register one of add-on modules

So I think this could actually achieve the goal of minimal annotations, without suddenly starting to use constructors with upgrade to 2.5.

What do you think?

... and, if that sounds good, we would then get into related discussion of "But which constructor should be used"? :)
 
-+ Tatu +-

(*) Single-[string/int/long/boolean)-argument constructors are actually auto-detected, and used as "delegating" creators; once that map underlying JSON String/Number/Boolean, and hence does not need a matching property name.

Lovro Pandzic

unread,
Oct 8, 2014, 12:16:55 PM10/8/14
to jacks...@googlegroups.com
I'm not sure I understand the difference betwen 1.a and 1.b. B) requires that the JSON object exactly matches the parameters?

Tatu Saloranta

unread,
Oct 8, 2014, 5:07:16 PM10/8/14
to jacks...@googlegroups.com
On Wed, Oct 8, 2014 at 9:16 AM, Lovro Pandzic <lovro....@gmail.com> wrote:
I'm not sure I understand the difference betwen 1.a and 1.b. B) requires that the JSON object exactly matches the parameters?

It means that interpretation is different. In 1a, the whole value (JSON Object, Number, String) must map directly to declared type of the one non-named parameter. Common use is something like:

   @JsonCreator
   public MyType(Map<String,Object> properties) {
       /// extract values
   }

to just take in a JSON Object, let Jackson bind it as a Map<String,Object>, and then extract bits and pieces by code in constructor.
No name is expected or used in this case.

Another typical use is for mapping JSON Strings and Numbers:

   @JsonCreator
   protected MyScalarType(String str) {  .... }

1b, on the other hand, expects input to be a JSON Object, and it maps properties thereof one by one, into declared types:


   @JsonCreator
   public NamedThing(@JsonProperty("name") String name) { ... }

So far, when requiring explicit names for parameters, it has been possible to tell apart case where there is exactly one parameter. But with implicit names, there would always be a name available.
It it also possibly to just require passing of one property via creator, and rest through setters, with this style of creators.

-+ Tatu +-


 

--
You received this message because you are subscribed to the Google Groups "jackson-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-dev...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Christopher Currie

unread,
Oct 28, 2014, 4:04:42 PM10/28/14
to Tatu Saloranta, jacks...@googlegroups.com
For Scala's purposes, (1) is the only blocking issue. Scala's language rules dictate that each class have a primary constructor that can be identified through reflection, so for (2) we'll simply have `AnnotationIntrospector` report true for `hasCreatorAnnotation`. I'd prefer to separate these issues, as I don't need a solution for (2).

With respect to the 1-argument creator: I struggle, probably because the solution for the use cases I'm running to *seem* obvious, but I suspect they require look-ahead infrastructure that databind doesn't have. In the cases I care about, the incoming JSON is obviously an object that contains a property that matches the implicit parameter name. This is, of course, not a perfect heuristic, because you may be delegating to a different type that happens to have a matching constructor that has an explicit name.

I obviously don't want to break behavior for existing users, and I can accept a solution that uses `AnnotationIntrospector` to allow the Scala module to change the default from `default` to `property`, which IIUC is how the Scala module behaves for 2.4. For the 3.x timeframe, though, I would love for 1-arg ctors to no be a special case, and to require `@JsonValue` or some other annotation in order to activate the delegating case. This might also solve the problem of asymmetry between serialization and deserialization.

As an aside, I tried to fake the problem by having the Scala module declare the names as explicit names. This turns out to not work, because the names are not then processed by the PropertyNamingStrategy, under the valid assumption that explicit names are assumed correct. 2.3's solution was to use a subclass of POJOPropertyBuilder, and this was never updated for 2.4's implicit property name support; it overrode the base class's `_renameUsing` so that explicit names were also renamed. This likely means that the fix for https://github.com/FasterXML/jackson-databind/issues/428 doesn't apply to the Scala module.




On Tue, Oct 28, 2014 at 11:14 AM, Tatu Saloranta <ta...@fasterxml.com> wrote:
Ok, I am hoping to continue this thread. At this point I think we have multiple modules (JDK8, Kotlin, Scala, Paranamer) that will be affected for 2.5.

Going back to two problems I have outlined earlier:

(1) How to decide between "delegating" and "property-based" creator, for single-argument method, use of implicit names; and
(2) More general auto-detection of creators without having to use @JsonCreator, using implicit names provided by (JDK8 / paranamer / Scala / Kotlin)

I think that for (1), addition of property for @JsonCreator would work. Something with at least three values (delegating, property-based, default -- last meaning "use heuristics") would allow explicit override for ambiguous cases. Heuristics here could be improved, perhaps (say, considering existing of "@JsonValue" suggesting delegate as the default), but can also complicate trouble-shooting in problem cases. The simple possibility would be to use existing rule of "if explicit name, property-based; if not, delegating".
Regardless, to detect proper mode, a new method is needed in AnnotationIntrospector; and overriding this method would allow Scala-, JDK-8 et al to change detection logic and defaulting.
In the end, it may be necessary to just document the fact that single-argument constructors are a special case, and that more care will be -- unfortunately -- needed.

The question of (2) is still somewhat open, however. Problems that I see are:

(a) In general, existence of even public constructors is not (IMO) a strong(-enough) signal of intent to use those for data-binding.
(b) The way Jackson creators work, you can only have a single resolved creator to use for property-based creators (it is possible to have multiple for delegating-model, iff they use different underlying JSON type -- String, Number, Object). This is not going to easy to change, although it is a theoretical possibility
(c) In case of multiple potential property-based creators, it is not clear how to choose optimal one -- use of longest one is one heuristic, but as with (a), I am not sure I'd trust the intent.

The biggest thing, really, is that switch to using JDK8 could unexpectedly trigger use of a constructor that was, say, only meant to be used for testing. I don't like introducing breaking changes, especially sneaky ones.

But assuming that there is a new feature to enable more advanced auto-detection -- something that I would support -- does not solve the question of choosing a single optimal property-based constructor.

-+ Tatu +-

ps. I am bit torn between separating two "big question" (how to choose style/mode for 1-argument creator, in absence of @JsonCreator vs how to choose between multiple potential N-argument constructors) -- they are related, but both are big enough to discuss separately.




--
@('_')@
Christopher Currie <codem...@gmail.com>

Tatu Saloranta

unread,
Oct 31, 2014, 11:49:25 AM10/31/14
to jacks...@googlegroups.com, Christopher Currie
Ok, I am hoping to continue this thread. At this point I think we have multiple modules (JDK8, Kotlin, Scala, Paranamer) that will be affected for 2.5.

Going back to two problems I have outlined earlier:

(1) How to decide between "delegating" and "property-based" creator, for single-argument method, use of implicit names; and
(2) More general auto-detection of creators without having to use @JsonCreator, using implicit names provided by (JDK8 / paranamer / Scala / Kotlin)

I think that for (1), addition of property for @JsonCreator would work. Something with at least three values (delegating, property-based, default -- last meaning "use heuristics") would allow explicit override for ambiguous cases. Heuristics here could be improved, perhaps (say, considering existing of "@JsonValue" suggesting delegate as the default), but can also complicate trouble-shooting in problem cases. The simple possibility would be to use existing rule of "if explicit name, property-based; if not, delegating".
Regardless, to detect proper mode, a new method is needed in AnnotationIntrospector; and overriding this method would allow Scala-, JDK-8 et al to change detection logic and defaulting.
In the end, it may be necessary to just document the fact that single-argument constructors are a special case, and that more care will be -- unfortunately -- needed.

The question of (2) is still somewhat open, however. Problems that I see are:

(a) In general, existence of even public constructors is not (IMO) a strong(-enough) signal of intent to use those for data-binding.
(b) The way Jackson creators work, you can only have a single resolved creator to use for property-based creators (it is possible to have multiple for delegating-model, iff they use different underlying JSON type -- String, Number, Object). This is not going to easy to change, although it is a theoretical possibility
(c) In case of multiple potential property-based creators, it is not clear how to choose optimal one -- use of longest one is one heuristic, but as with (a), I am not sure I'd trust the intent.

The biggest thing, really, is that switch to using JDK8 could unexpectedly trigger use of a constructor that was, say, only meant to be used for testing. I don't like introducing breaking changes, especially sneaky ones.

But assuming that there is a new feature to enable more advanced auto-detection -- something that I would support -- does not solve the question of choosing a single optimal property-based constructor.

-+ Tatu +-

ps. I am bit torn between separating two "big question" (how to choose style/mode for 1-argument creator, in absence of @JsonCreator vs how to choose between multiple potential N-argument constructors) -- they are related, but both are big enough to discuss separately.

On Wed, Oct 8, 2014 at 2:07 PM, Tatu Saloranta <ta...@fasterxml.com> wrote:

Tatu Saloranta

unread,
Nov 17, 2014, 8:27:17 PM11/17/14
to Christopher Currie, jacks...@googlegroups.com
Ok, I finally found some time to work on getting this forward, and as per:

https://github.com/FasterXML/jackson-databind/issues/614

there is now @JsonCreator.mode property, and matching method in AnnotationIntrospector that is called by BasicBeanDeserializerFactory to see if there is explicit override for choice.
In addition, I decided to add one heuristic that could help further disambiguate the choice: if the single argument has implicit name, AND there is a getter or field for same name, assumption is made that we have a property-based creator. Formerly this would have resulted in delegating creator.
Explicit name will still lead to property-based creator (unless overridden by the new property).

So: this should solve the easier problem, (1), both for users, and hopefully for at least some of the modules.

Problem (2) still remains, but at least there should be one less related problem to solve.

-+ Tatu +-

Christopher Currie

unread,
Nov 17, 2014, 8:28:46 PM11/17/14
to Tatu Saloranta, jacks...@googlegroups.com
This is excellent news. I'll start looking at finalizing the Scala module updates this evening.
Reply all
Reply to author
Forward
0 new messages