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

Cannot detect creator arguments of mixins for JDK types #2795

Closed
marcospassos opened this issue Jul 13, 2020 · 12 comments
Closed

Cannot detect creator arguments of mixins for JDK types #2795

marcospassos opened this issue Jul 13, 2020 · 12 comments
Milestone

Comments

@marcospassos
Copy link
Contributor

I've upgraded our project from 2.10.1 to 2.11.1 and a test case is now failing. For for weird reason, a static constructor defined via mixin no longer works:

Mixin:

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.NONE,
    getterVisibility = JsonAutoDetect.Visibility.NONE,
    isGetterVisibility = JsonAutoDetect.Visibility.NONE,
    setterVisibility = JsonAutoDetect.Visibility.NONE,
    creatorVisibility = JsonAutoDetect.Visibility.NONE
)
public abstract class DurationMixin {
  @JsonCreator
  public static void ofSeconds(@JsonProperty("seconds") long seconds, @JsonProperty("nano") long nanoAdjustment) {
  }

  @JsonGetter("seconds")
  public abstract long getSeconds();

  @JsonGetter("nano")
  public abstract int getNano();
}

Module:

context.setMixInAnnotations(Duration.class, DurationMixin.class);

Error:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `java.time.Duration`: Argument #0 has no property name, is not Injectable: can not use as Creator [method java.time.Duration#ofSeconds(2 params)]
 at [Source: (String)"{"seconds":1,"nano":2}"; line: 1, column: 1]

	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadTypeDefinition(DeserializationContext.java:1593)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitPropertyCreator(BasicDeserializerFactory.java:630)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addExplicitAnyCreator(BasicDeserializerFactory.java:661)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addDeserializerFactoryMethods(BasicDeserializerFactory.java:814)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:280)
	at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:224)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:220)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:143)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:414)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
	at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
	at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:491)
	at com.fasterxml.jackson.databind.ObjectReader._findRootDeserializer(ObjectReader.java:2388)
	at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2052)
	at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1496)
	at com.croct.platform.domain.serialization.standard.JacksonStandardMixinTest.assertJsonSerializable(JacksonStandardMixinTest.java:68)
	at com.croct.platform.domain.serialization.standard.JacksonStandardMixinTest.assertSerializable(JacksonStandardMixinTest.java:24)
	at com.croct.platform.domain.serialization.standard.DurationMixinTest.shouldSupportDuration(DurationMixinTest.java:11)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$144/0000000000000000.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall$$Lambda$145/0000000000000000.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$$Lambda$268/0000000000000000.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$284/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$217/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$216/0000000000000000.invoke(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$215/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$221/0000000000000000.accept(Unknown Source)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$217/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$216/0000000000000000.invoke(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$215/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$221/0000000000000000.accept(Unknown Source)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$217/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$216/0000000000000000.invoke(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$215/0000000000000000.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
	at org.junit.platform.launcher.core.DefaultLauncher$$Lambda$174/0000000000000000.accept(Unknown Source)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

Any idea on what can have caused this BC break?

@cowtowncoder
Copy link
Member

I don't think this would be intentional change, but I think I would need full reproduction.
I would guess this to be a regression due to refactoring of introspection code, possibly a case where an optimization was made to reduce amount of information to gather (exception suggests @JsonProperty annotations were missing for parameters, but @JsonCreator was seen).

On reproduction: if possible, it would be good to use a POJO type instead Java 8 date/time type -- I suspect that should work as well.

@marcospassos
Copy link
Contributor Author

That's interesting. We've exactly the same case (static constructor in mixins) for POJO and it has not been affected.

@cowtowncoder
Copy link
Member

@marcospassos that is interesting indeed.

@marcospassos
Copy link
Contributor Author

marcospassos commented Jul 13, 2020

Another interesting fact: other mixins for standard classes, such as InetAddress, work as expected. The only difference is that the duration's constructor method has two arguments.

@cowtowncoder
Copy link
Member

Was about to suggest something about JDK classes, maybe inclusion (or not) of creator parameter names (although with annotations there, should not matter), but that should not matter then./
Another possibility could be use of a codegen-using framework like Lombok, which sometimes might have different handling wrt adding annotation on generated classes?

@cliffred
Copy link

cliffred commented Sep 3, 2020

I have the exact same issue with a mixin for LocalTime and also Duration. While debugging I found the issue is in
com.fasterxml.jackson.databind.introspect.AnnotatedCreatorCollector. In 2.11 the _collectAnnotations boolean was added (cef4b99) and this boolean is false for JDK classes. And when this boolean is false the collectAnnotations method returns an empty annotations map, instead of the @JsonProperty annotations from the mixin.

@cowtowncoder
Copy link
Member

Ahh. Hmmh. Yes, I was afraid this attempted optimization to reduce startup costs might backfire.

Tackling this issue is bit tricky just wrt reproduction: Java 8 date/time types can not be tested from 2.x databind (Java 8 dependencies not allowed in 2.x yet). I am also not sure how valuable ability to annotated those is, mostly because default Java 8 date/time module will not make any use of them.
But then again it does not seem completely unreasonable for someone to try to use mix-in annotations for some rare JDK types (or, maybe, new types in later JDKs not yet support by Jackson).

@cowtowncoder
Copy link
Member

I guess it'd make sense to just apply mix-ins for JDK types (since they themselves can't have any)... but for that, type itself does need to be processed and optimization might not be worth much.

At any rate, having a test for JDK type available in JDK 7 or earlier, not handled by explicit deserializer, would make sense.

@ausema
Copy link

ausema commented Sep 7, 2020

Hi,
We have a similar problem with some immutable collections. I have tried @cliffred solution, forcing in debug mode collectAnnotations to be true, and our test passed.
this is our object mapper configuration

    public ObjectMapper objectMapper(){
        return configureMapperForImmutableObjects(new ObjectMapper());
    }
    private ObjectMapper configureMapperForImmutableObjects(final ObjectMapper mapper) {
        return mapper.setVisibility(
                mapper.getVisibilityChecker()
                        .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
                        .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                        .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
                        .withCreatorVisibility(JsonAutoDetect.Visibility.NONE))
                .activateDefaultTypingAsProperty(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, "@class")
                .registerModules(
                        new ImmutableCollectionsModule()
                )
                .setSerializationInclusion(JsonInclude.Include.NON_DEFAULT)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

This is our module

context.setMixInAnnotations(Collections.unmodifiableSortedMap(Collections.emptySortedMap()).getClass(), UnmodifiableSortedMapMixin.class);
context.setMixInAnnotations(Collections.unmodifiableCollection(Collections.emptyList()).getClass(), UnmodifiableCollectionMixin.class);
context.setMixInAnnotations(Collections.unmodifiableNavigableMap(Collections.emptyNavigableMap()).getClass(), UnmodifiableNavigableMapMixin.class);

And this is the Mixin

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
class UnmodifiableSortedMapMixin {

    @JsonCreator
    public UnmodifiableSortedMapMixin(final SortedMap<?, ?> map) {
    }
}

our test passed with jacksson version 2.10.5 and failed with 2.11.2 version.
You can find the full code here https://github.com/ausema/TestJacksonDeserialization

@cowtowncoder
Copy link
Member

@ausema Compatibility breakage is unfortunate, but I will have to say that usage as shown is very very fragile and could quite easily break when JDK changed its implementation of collection types accessible via Collections.
So I would not recommend trying to do that in the first place.

That said I hope this can still be addressed.

@cowtowncoder
Copy link
Member

Ok, so, first fix in (for 2.11.3) and makes @ausema's case work at least. Will see if that also solves Java 8 date/time, and if not, what to do there.

@cowtowncoder cowtowncoder added this to the 2.11.3 milestone Oct 1, 2020
@cowtowncoder cowtowncoder changed the title [BC Break] - Cannot detect creator arguments Cannot detect creator arguments of mixins for JDK types Oct 1, 2020
cowtowncoder added a commit that referenced this issue Oct 1, 2020
@cowtowncoder
Copy link
Member

Ok, some good and bad news wrt @marcospassos' original issue:

  1. Issue wrt annotations is fixed so 2.11.3 should work like 2.10.x wrt java.time types
  2. Jackson 2.12 adds Explicitly fail (de)serialization of java.time.* types in absence of registered custom (de)serializers #2683 and with that things will break for java.time UNLESS one registers actual (de)serializers -- mix-ins are not enough

I'll have to think about this a bit. Adding a MapperFeature (on whether to give meant-to-be-helpful fail on missing module) is an option but there are some problems there (aside from it seeming sort of wrong thing).

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

No branches or pull requests

4 participants