jackson-module-kotlin: 2.12.x regression: no default no-arguments constructor found

I have this Kotlin object which parses an XML into a data class:

object XmlTool {

    @JvmStatic
    fun parseXml(xml: String?): Product {
        val xmlIn = XMLInputFactory.newInstance()
        val factory = XmlFactory(xmlIn)
        val xmlModule = JacksonXmlModule()

        val mapper = XmlMapper(factory, xmlModule).registerKotlinModule()

        return mapper.readValue(xml, Product::class.java)
    }

    data class Stuff(val str: String?)
    data class Product(val stuff: Stuff?)
}

And this Java test:

class Jackson212MissingConstructorTest {

    @Test
    void fails_with_jackson_2_12() throws Exception {
        String xml = "<product><stuff></stuff></product>";

        Product product = XmlTool.parseXml(xml);

        assertEquals(new Product(null), product);
    }
}

Parsing this in Jackson 2.12.0 fails with com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of jackson.xml.XmlTool$Stuff (although at least one Creator exists): no default no-arguments constructor found.

Jackson 2.11.3 works just fine.

The full exception is:

jackson.xml.Jackson212MissingConstructorTest > fails_with_jackson_2_12() 
    com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `jackson.xml.XmlTool$Stuff` (although at least one Creator exists): no default no-arguments constructor found
     at [Source: (StringReader); line: 1, column: 17] (through reference chain: jackson.xml.XmlTool$Product["stuff"])
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
        at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1590)
        at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1215)
        at com.fasterxml.jackson.databind.deser.ValueInstantiator.createUsingDefault(ValueInstantiator.java:248)
        at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:275)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.getEmptyValue(BeanDeserializerBase.java:1027)
        at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromEmptyString(StdDeserializer.java:322)
        at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:271)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1480)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:565)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:449)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1390)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:362)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195)
        at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4568)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3523)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3491)
        at jackson.xml.XmlTool.parseXml(XmlTool.kt:19)
        at jackson.xml.Jackson212MissingConstructorTest.fails_with_jackson_2_12(Jackson212MissingConstructorTest.java:14)

Here’s a test project: https://github.com/henrik242/jackson-xml-problem/tree/2-12-empty-constructor

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 4
  • Comments: 52 (28 by maintainers)

Commits related to this issue

Most upvoted comments

@henrik242 I think I’ll first transfer this to Kotlin module, for maintainers to see.

Weird. It works out of the box in IntelliJ IDEA. Haven’t used Eclipse in ages, so I can’t help there 😕

EDIT: I tried the latest Eclipse IDE, but couldn’t get it to work. The kotlin plugin had problems that I was unable to resolve, and the scala plugin isn’t supported anymore.

@henrik242 I think release will be sooner than I originally planned, due to one particular CVE. But I think we are still out by at least a month since first release candidate. Still, lots of good fixes esp. for Java record types, for 2.15.

@henrik242 Thanks for following up with that—such links of information are very helpful.

I don’t think this is a Kotlin-specific problem. I have managed to reproduce the regression in the below Java snippet, which works in 2.11.4, but fails in 2.12.x:

package com.vlkan.jackson;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.junit.jupiter.api.Assertions.assertEquals;

class MixinTest {

    interface Problem {

        String DEFAULT_TYPE = "about:blank";

        int DEFAULT_STATUS = 500;

        String getType();

        int getStatus();

    }

    static class DefaultProblem implements Problem {

        private final String type;

        private final int status;

        /**
         * This is required to workaround Jackson's missing support for static
         * {@link JsonCreator}s in mix-ins. That is, we need to define the
         * creator on a constructor in the mix-in that is matching with a
         * constructor here too.
         *
         * @see <a href="https://github.com/FasterXML/jackson-databind/issues/1820">jackson-databind issue 1820</a>
         */
        DefaultProblem(String type, Integer status) {
            this.type = type != null ? type : Problem.DEFAULT_TYPE;
            this.status = status != null ? status : Problem.DEFAULT_STATUS;
        }

        @Override
        public String getType() {
            return type;
        }

        @Override
        public int getStatus() {
            return status;
        }

    }

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXISTING_PROPERTY,
            property = "type",
            defaultImpl = DefaultProblem.class,
            visible = true)
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @JsonRootName("problem")
    interface ProblemMixIn extends Problem {

        @Override
        @JsonProperty("type")
        String getType();

        @Override
        @JsonProperty("status")
        int getStatus();

    }

    abstract static class DefaultProblemMixIn extends DefaultProblem {

        @JsonCreator
        DefaultProblemMixIn(
                @JsonProperty("type") String type,
                @JsonProperty("status") Integer status) {
            super(type, status);
            throw new IllegalStateException(
                    "mix-in constructor is there only for extracting the JSON mapping, " +
                            "it should not have been called");
        }

    }

    static class ProblemModule extends SimpleModule {

        @Override
        public void setupModule(SetupContext context) {
            super.setupModule(context);
            registerMixIns(context);
        }

        private static void registerMixIns(SetupContext context) {
            context.setMixInAnnotations(DefaultProblem.class, DefaultProblemMixIn.class);
            context.setMixInAnnotations(Problem.class, ProblemMixIn.class);
        }

    }

    private static final ProblemModule MODULE = new ProblemModule();

    private static final ObjectMapper JSON_MAPPER = new ObjectMapper().registerModule(MODULE);

    private static final XmlMapper XML_MAPPER = (XmlMapper) new XmlMapper().registerModule(MODULE);

    @Test
    void test_empty_Problem_JSON_deserialization() throws IOException {
        byte[] problemJsonBytes = "{}".getBytes(StandardCharsets.UTF_8);
        Problem problem = JSON_MAPPER.readValue(problemJsonBytes, Problem.class);
        assertEquals(Problem.DEFAULT_TYPE, problem.getType());
        assertEquals(Problem.DEFAULT_STATUS, problem.getStatus());
    }

    @Test
    void test_empty_Problem_XML_deserialization() throws IOException {
        byte[] problemXmlBytes = "<problem/>".getBytes(StandardCharsets.UTF_8);
        Problem problem = XML_MAPPER.readValue(problemXmlBytes, Problem.class);
        assertEquals(Problem.DEFAULT_TYPE, problem.getType());
        assertEquals(Problem.DEFAULT_STATUS, problem.getStatus());
    }

}

Both tests pass on 2.11.4, whereas, on 2.12.x, test_empty_Problem_JSON_deserialization() passes and test_empty_Problem_XML_deserialization() fails with the following message:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.vlkan.jackson.MixinTest$DefaultProblem` (although at least one Creator exists): no default no-arguments constructor found
 at [Source: (byte[])"<problem/>"; line: 1, column: 1]

	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1588)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1213)
	at com.fasterxml.jackson.databind.deser.ValueInstantiator.createUsingDefault(ValueInstantiator.java:248)
	at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:275)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.getEmptyValue(BeanDeserializerBase.java:1042)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromEmptyString(StdDeserializer.java:322)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:270)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1495)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:194)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:96)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
	at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3609)
	at com.vlkan.jackson.MixinTest.test_empty_Problem_XML_deserialization(MixinTest.java:129)
        ...

In order to make test_empty_Problem_XML_deserialization() pass on 2.12.x, one needs to add a no-arg ctor to DefaultProblem.

@patrick-dedication A minimal example (or a failing test) is always good to have! Interesting that you have found the same issue with JSON.

No fix in 2.12.2 either

@cowtowncoder We actually already have the jackson-dataformat-xml module in the Kotlin module’s test dependencies, so I added @henrik242 's test to this module’s test suite.

I haven’t dug into what’s actually going wrong, happy to take tips or PRs against that branch for a fix.

@cowtowncoder @michaelbrewer @dinomite I’ve added an integration test for this now

Typically first .1 patch follows in about 1 month of the .0, depending on accumulation of fixes. I am trying to take it easy over December anyway, but I think it is likely that 2.12.1 would be released around end of 2020, either last week of Dec or first of Jan 2021.

As to regression test: if the test requires both XML and Kotlin modules, it should to go in:

https://github.com/FasterXML/jackson-integration-tests

to avoid adding cross-component dependencies (beyond existing compile-time dependencies to core components). This assuming it is not possible to reproduce this without xml module (or kotlin module).

@cowtowncoder @dinomite The 2.12 release fails with this issue now.