Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore ConstructorDetector.SingleArgConstructor.PROPERTIES preference for Records. #3969

Conversation

yihtserns
Copy link
Contributor

@yihtserns yihtserns commented Jun 7, 2023

To fix #3968.

NOTE: I also wanted to add test case to see what happens when the canonical constructor is @JsonIgnore-ed, but apparently that's (currently) not allowed because it will throw exception:

if (primary == null) {
throw new IllegalArgumentException("Failed to find the canonical Record constructor of type "
+ClassUtil.getTypeDescription(_recordClass.getType()));
}

@yihtserns yihtserns force-pushed the records-paramsmodule-singleval-useprops-break branch from bc51a21 to 7cf3bc0 Compare June 7, 2023 13:40
@yihtserns yihtserns changed the base branch from 2.16 to 2.15 June 7, 2023 16:28
@@ -482,7 +482,9 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt,
final AnnotationIntrospector intr = ccState.annotationIntrospector();
final VisibilityChecker<?> vchecker = ccState.vchecker;
List<AnnotatedWithParams> implicitCtors = null;
final boolean preferPropsBased = config.getConstructorDetector().singleArgCreatorDefaultsToProperties();
final boolean preferPropsBased = config.getConstructorDetector().singleArgCreatorDefaultsToProperties()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this based on semantics needing changing, or just that it happens to solve the failure case?
My understanding is that for Records, default of Mode.PROPERTIES is something we would want, not Mode.DELEGATING?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that for Records, default of Mode.PROPERTIES is something we would want, not Mode.DELEGATING?

For when the constructor has single-arg, canonical constructor will already be recognised as properties-based creator by:

if (propDef != null) {
// One more thing: if implicit name matches property with a getter
// or field, we'll consider it property-based as well
String implName = propDef.getName();
if (implName != null && !implName.isEmpty()) {
if (propDef.couldSerialize()) {
return true;
}
}

...because only a canonical constructor will have an implicit constructor + propDef with implicit name.

This ConstructorDetector config would only affect non-canonical constructors (which currently are/should only be considered for delegating creators).

Additional notes: Currently the only way to "implicitly" make canonical constructor a delegating creator is via @JsonValue:

record RecordWithSingleValueConstructorWithJsonValue(@JsonValue int id) {
}
record RecordWithSingleValueConstructorWithJsonValueAccessor(int id) {
@JsonValue
@Override
public int id() {
return id;
}
}

@@ -482,7 +482,9 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt,
final AnnotationIntrospector intr = ccState.annotationIntrospector();
final VisibilityChecker<?> vchecker = ccState.vchecker;
List<AnnotatedWithParams> implicitCtors = null;
final boolean preferPropsBased = config.getConstructorDetector().singleArgCreatorDefaultsToProperties();
final boolean preferPropsBased = config.getConstructorDetector().singleArgCreatorDefaultsToProperties()
// [databind#3968]: Only Record's canonical constructor is allowed to be considered for properties-based creator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean, implicitly? And this because by default, canonical constructor is already considered (usually) Properties-based?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, canonical constructor will be recognised as properties-based creator by:

  1. if (propDef != null) {
    // One more thing: if implicit name matches property with a getter
    // or field, we'll consider it property-based as well
    String implName = propDef.getName();
    if (implName != null && !implName.isEmpty()) {
    if (propDef.couldSerialize()) {
    return true;
    }
    }
  2. if ((propDef != null)
    // [databind#3724]: Record canonical constructor will have implicitly named propDef
    && (propDef.isExplicitlyNamed() || beanDesc.isRecordType())) {
    ++explicitNameCount;
    properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectable);
    continue;
    }

...because only a canonical constructor will have an implicit constructor + propDef with implicit name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I think I understand how this works around the issue, although it still seems like it shouldn't be needed (but is due to... combination of factors). But since it actually resolves an actual problem & does not break other tests, I think I should merge this. And revisit whole thing for 2.16; but not block 2.15.x.

@cowtowncoder
Copy link
Member

Ok, added some questions -- I think I understand the challenge, just seems tricky to combine defaulting of creator Mode (where Records definitely should default to Properties). But I understand that the issue then is just the conflict between 2 alternative Creators of same type.

However... for non-annotated ("implicit") case the Canonical constructor should win, I think; and in case of annotated (explicit) that one.

So I just want to try to make sure logic does not get even more fragile with various extra checks.

I hope to review this within 24 hours or so, but wanted add a quick note first.

@JooHyukKim
Copy link
Member

In this case, perhaps the unification of POJO handling (POJOs do not have canonical constructors) and Records caused more problems than it's worth... again. :-/

After seeing a couple of additional checks to fix regressions, I'm considering the need to discuss risks, backup plans, or at least warnings for users, especially for those who use record class heavily.

  • Warning : should users be informed?
  • Back-up, is there any back-up plan? revert? another refactor?

NOTE: Maybe this is not a right place to share this?

@cowtowncoder
Copy link
Member

@JooHyukKim Alas, when we have all existing issues, I am not sure how much users will benefit from more pro-active communication outside of issue updates (if I understood this correctly). It's very tricky situation of course, esp. with patch releases... we don't have mechanism for pre-releases (nor do users really use those even if we did). SNAPSHOT builds exist, but are rarely used too. Test coverage works for regressions but not use cases users have -- and users rarely contribute tests for theirs, not until they break :-( :-( :-(

@cowtowncoder
Copy link
Member

For what it is worth I do plan on now finally tackling introspection rewrite for 2.16. It may be risky but at least we do have much improved test suite, as of 2.15.x. And -- I hope -- better understanding of challenges.

@cowtowncoder cowtowncoder merged commit 24c1e30 into FasterXML:2.15 Jun 13, 2023
@cowtowncoder
Copy link
Member

Thank you @yihtserns !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Records with additional constructors failed to deserialize
3 participants