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

@JacksonInject will try to fetch deserializer for injected creator property if there is visible field #2465

Open
XakepSDK opened this issue Sep 19, 2019 · 6 comments
Labels
has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue property-discovery Problem with property discovery (introspection)

Comments

@XakepSDK
Copy link
Contributor

XakepSDK commented Sep 19, 2019

Everything works as expected if you remove

.withVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)

or

mapper.setDefaultSetterInfo(JsonSetter.Value.construct(Nulls.AS_EMPTY, Nulls.AS_EMPTY));

Otherwise error thrown:
Cannot create empty instance of [simple type, class dk.xakeps.jacksontest.Test$Internal], no default Creator

jackson-databind 2.9.9.3
Test class:

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        mapper.setDefaultSetterInfo(JsonSetter.Value.construct(Nulls.AS_EMPTY, Nulls.AS_EMPTY));
        mapper.setVisibility(mapper.getVisibilityChecker().withVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY));

        TestCase o = mapper.reader(new InjectableValues.Std().addValue(Internal.class, new Internal("test")))
                .forType(TestCase.class)
                .readValue("{\"id\":3}");
        System.out.println(o.str);
    }

    public static final class TestCase {
        private final Internal str;
        private final int id;

        @JsonCreator
        public TestCase(@JacksonInject Internal str, @JsonProperty("id") int id) {
            this.str = str;
            this.id = id;
        }
    }

    public static final class Internal {
        private final String val;

        public Internal(String val) {
            this.val = val;
        }
    }
}
@XakepSDK XakepSDK changed the title Injectable bug field visible any Injectable bug field visibility any Sep 19, 2019
@XakepSDK XakepSDK changed the title Injectable bug field visibility any Injectable bug field visibility any and non-null Sep 19, 2019
@XakepSDK
Copy link
Contributor Author

Looks like it only tries to find empty constructor. If you add empty constructor, everything will work fine. Empty constructor not called.

This works fine:

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        mapper.setDefaultSetterInfo(JsonSetter.Value.construct(Nulls.AS_EMPTY, Nulls.AS_EMPTY)); // not working?
        mapper.setVisibility(mapper.getVisibilityChecker().withVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY));

        TestCase o = mapper.reader(new InjectableValues.Std().addValue(Internal.class, new Internal("test")))
                .forType(TestCase.class)
                .readValue("{\"id\":3}");
        System.out.println(o.str.val);
    }

    public static final class TestCase {
        private final Internal str;
        private final int id;

        @JsonCreator
        public TestCase(@JacksonInject Internal str, @JsonProperty("id") int id) {
            this.str = str;
            this.id = id;
        }
    }

    public static final class Internal {
        private final String val;

        public Internal() {
            if (true) throw new RuntimeException("Can't call this");
            val = null;
        }

        public Internal(String val) {
            this.val = val;
        }
    }
}

@cowtowncoder
Copy link
Member

Interesting. Thank you for providing all the information -- I hope to look into this in future, but right now there is a backlog of issues so it may take a while. But I may be able to add failing unit test quickly.

cowtowncoder added a commit that referenced this issue Sep 19, 2019
@cowtowncoder
Copy link
Member

Based on symptoms, I think that lack of setter/field (unless overridden by visibility to find it) somehow triggers the problem: addition of "set as 'empty' if 'null' passed" will trigger the exception but probably would just mask the real problem.
This to help further diagnostics: failing tests exists under .../failing/ now.

@cowtowncoder cowtowncoder added 2.12 and removed 2.10 labels Apr 7, 2020
@cowtowncoder
Copy link
Member

Ok, some semi-good news. With fixing of #962, 2.11.0 will have a way to prevent the issue.
Solution is two-part:

First @JacksonInject has property useInput (added in 2.9), used like

 @JacksonInject(useInput = OptBoolean.FALSE)

and this should be used to avoid having to find out deserializer since value will never be read from input (content being bound). This is needed regardless.

However, although this should be enough in and of itself, there is a problem with field str being associated with creator property; databind will still insist on deserializer since it does not quite connect the dots. But until that issue is resolved (and for which I will leave this issue open), you can explicitly ignore it with:

        @JsonIgnore
        private final Internal2465 str;

and that combination should avoid the problem, once 2.11.0 is released.

@cowtowncoder cowtowncoder changed the title Injectable bug field visibility any and non-null @JacksonInject will try to fetch deserializer for injected creator property if there is visible field Apr 18, 2020
@cowtowncoder
Copy link
Member

One sidenote: Jackson 3.0 does not seem to fail for the test, for whatever reason.

@cowtowncoder cowtowncoder added 2.13 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue and removed 2.12 labels Oct 27, 2020
@solonovamax
Copy link

I found this issue on my own (see the now-closed #3124), and here's a smaller test, if you want that:

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

public class CursedInjectionDeserializerTest {
    
    @Test
    void testInjectionBlackMagic() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setInjectableValues(new InjectableValues.Std().addValue(Injected.class, new Injected()));
        
        TestCase test = mapper.readValue("{\"id\": 3}", TestCase.class);
        System.out.println(test.id);
    }
    
    
    public static final class TestCase {
        private final Injected injected;
        private final int      id;
        
        public TestCase(@JacksonInject Injected injected,
                        @JsonProperty("id") int id) {
            this.injected = injected;
            this.id = id;
        }
    }
    
    public static final class Injected {
        private void setAbcd(Void a) {}
        
        private void setAbcd(Boolean a) {}
    }
}

I used some parts of what I found in #3124, and the existing test case you had.

@cowtowncoder cowtowncoder added 2.14 and removed 2.13 labels Jul 15, 2021
@cowtowncoder cowtowncoder added property-discovery Problem with property discovery (introspection) and removed 2.14 labels Nov 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue property-discovery Problem with property discovery (introspection)
Projects
None yet
Development

No branches or pull requests

3 participants