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

Failure to read AnnotatedField value in Jackson 2.11 #2789

Closed
isaki opened this issue Jul 8, 2020 · 8 comments
Closed

Failure to read AnnotatedField value in Jackson 2.11 #2789

isaki opened this issue Jul 8, 2020 · 8 comments
Milestone

Comments

@isaki
Copy link

isaki commented Jul 8, 2020

I am reporting this issue for tracking purposes; I fully intend to submit a PR once I narrow down the root cause and come up with an acceptable fix.

Version Information

  • Java: Corretto-11.0.7.10.1 (OpenJDK 11; macOS), OpenJDK 8u252 (Ubuntu 16 Server)
  • Jackson: 2.11.1

Thankfully, this is failing in my unit tests on both my build environments so that makes this easy to debug.

com.fasterxml.jackson.databind.JsonMappingException: Failed to getValue() for field io.isaki.jsandbox.JacksonDatabindTest$DataParent#type: class com.fasterxml.jackson.databind.introspect.AnnotatedField cannot access a member of class io.isaki.jsandbox.JacksonDatabindTest$DataParent with modifiers "private final"
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._wrapAsIOE(DefaultSerializerProvider.java:509)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:482)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
	at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:4374)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3629)
	at io.isaki.jsandbox.JacksonDatabindTest.testAnnotatedField(JacksonDatabindTest.java:25)
	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.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: java.lang.IllegalArgumentException: Failed to getValue() for field io.isaki.jsandbox.JacksonDatabindTest$DataParent#type: class com.fasterxml.jackson.databind.introspect.AnnotatedField cannot access a member of class io.isaki.jsandbox.JacksonDatabindTest$DataParent with modifiers "private final"
	at com.fasterxml.jackson.databind.introspect.AnnotatedField.getValue(AnnotatedField.java:113)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase._typeIdDef(BeanSerializerBase.java:717)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeWithType(BeanSerializerBase.java:635)
	at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
	... 30 more
Caused by: java.lang.IllegalAccessException: class com.fasterxml.jackson.databind.introspect.AnnotatedField cannot access a member of class io.isaki.jsandbox.JacksonDatabindTest$DataParent with modifiers "private final"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
	at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
	at java.base/java.lang.reflect.Field.get(Field.java:416)
	at com.fasterxml.jackson.databind.introspect.AnnotatedField.getValue(AnnotatedField.java:110)
	... 34 more
import static org.junit.Assert.assertEquals;

import java.util.Objects;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeId;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonDatabindTest {

    @Test
    public void testAnnotatedField() throws JsonProcessingException {
        final DataParent test = new DataClassA();

        final ObjectMapper mapper = new ObjectMapper();
        final String json = mapper.writeValueAsString(test);

        final DataParent copy = mapper.readValue(json, DataParent.class);
        assertEquals(DataType.CLASS_A, copy.getType());
    }

    private enum DataType {
        CLASS_A;
    }

    @JsonAutoDetect(
        getterVisibility = JsonAutoDetect.Visibility.NONE,
        creatorVisibility = JsonAutoDetect.Visibility.NONE,
        isGetterVisibility = JsonAutoDetect.Visibility.NONE,
        fieldVisibility = JsonAutoDetect.Visibility.NONE,
        setterVisibility = JsonAutoDetect.Visibility.NONE)
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type",
        visible = true)
    @JsonSubTypes({
        @Type(name = "CLASS_A", value = DataClassA.class)
    })
    private static abstract class DataParent {

        @JsonProperty("type")
        @JsonTypeId
        private final DataType type;

        @SuppressWarnings("unused")
        DataParent() {
            super();
            this.type = null;
        }

        DataParent(final DataType type) {
            super();
            this.type = Objects.requireNonNull(type);
        }

        public final DataType getType() {
            return this.type;
        }
    }

    private static final class DataClassA extends DataParent {

        DataClassA() {
            super(DataType.CLASS_A);
        }
    }
}

EDIT: This test works fine with Jackson 2.9.10 (with 2.9.10.4).

@cowtowncoder
Copy link
Member

cowtowncoder commented Jul 8, 2020

Thank you for reporting this. It sounds like forcing of access is skipped for field type, when used as accessor for type id.

It seems bit odd that @JsonProperty is on private field, instead of public getType(), but usage as shown should work regardless.

@isaki
Copy link
Author

isaki commented Jul 8, 2020

This paradigm is common in the codebase I am working on; there are no setter methods. As such, it's easier to just place the annotations on the field directly so things are common between serialization and deserialization. Do you want me to take a stab at fixing it or do you already have a fix in mind?

@cowtowncoder
Copy link
Member

Ok. I am just asking because in this case it would seem preferable that getter was used for value access; normally this would just work with naming convention but here @JsonAutoDetect is used to explicitly prevent that.

I do not have a fix in mind so feel free to propose a fix if that is easy enough to find.
I think the expected place would be in BeanSerializerBuilder.

@isaki
Copy link
Author

isaki commented Jul 10, 2020

Just as a heads up I have not forgotten about this; the library upgrades that update my dependency on Jackson from 2.9.10 to 2.11.1 got sidelined for a little bit for a pressing business need. I'll take care of this next week once I get this change out the door.

cowtowncoder added a commit that referenced this issue Jul 17, 2020
@cowtowncoder
Copy link
Member

As per commit, added failing test from example above; might have a look to see if I can track down thge problem.

@cowtowncoder
Copy link
Member

Looks like the problem is due to short-circuit in BeanSerializerBuilder.build(), in:

        if (_properties == null || _properties.isEmpty()) {
            if (_anyGetter == null && _objectIdWriter == null) {
                return null;
            }
            ...
        }
        // .... 
        if (_typeId != null) {   // force access
        }

which will prevent _typeId access from being forced (if allowed).
Will need to see if just moving the access forcing earlier is the way to go.

@cowtowncoder cowtowncoder added this to the 2.11.2 milestone Jul 18, 2020
@cowtowncoder
Copy link
Member

Fixed: specifically occurred only when there are no properties to write (not any-getter); will now modify access for typeId first (if any).

@isaki
Copy link
Author

isaki commented Jul 20, 2020

Wow, thanks! I appreciate the fix!

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

2 participants