Skip to content

NPE deserializing Recursive Structure When Ignoring Properties (same as #1755) #4417

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

Closed
1 task done
hankolerd opened this issue Mar 6, 2024 · 4 comments
Closed
1 task done
Labels
duplicate Duplicate of an existing (usually earlier) issue has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue

Comments

@hankolerd
Copy link

hankolerd commented Mar 6, 2024

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

Disclaimer; This may be duplicate of #1755 - But thought I should raise separately in case root-cause is different, as that is raised against 2.9.

Starting with Jackson-Databind 2.12.0, attempting to deserialize a recursive data structure with a custom JacksonAnnotationIntrospector configured to ignore properties, for example:

new JacksonAnnotationIntrospector() {
   @Override
   public Value findPropertyIgnoralByName(MapperConfig<?> config, Annotated a) {
      return JsonIgnoreProperties.Value.forIgnoredProperties("CanBeAnyValue");
   }
}

A NullPointerException will be thrown during deserialization:

Caused by: java.lang.NullPointerException: Cannot invoke "com.fasterxml.jackson.databind.JsonDeserializer.getObjectIdReader()" because "valueDes" is null
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:333)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
	at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
	at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)

Changing the Value object to allow Setters will avoid the NullPointerException from occurring:

- return JsonIgnoreProperties.Value.forIgnoredProperties("CanBeAnyValue");
+ return JsonIgnoreProperties.Value.forIgnoredProperties("CanBeAnyValue").withAllowSetters();

Version Information

Affects starting from 2.12.0, confirmed issue still present through 2.17.0-rc1.

Reproduction

Here is a sample project to reproduce the issue
reproduce-project.zip

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>sample</groupId>
  <artifactId>test</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.0</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.10.2</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>17</source>
        <target>17</target>
      </configuration>    
      </plugin>
    </plugins>
  </build>
</project>
src/test/java/test/ReproduceTest.java
package sample;

import java.util.List;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties.Value;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

class ReproduceTest {

    /**
     * A simple recursive data structure to reproduce the issue
     */
    public static class Item {
        List<Item> items;
        public List<Item> getItems() {
            return items;
        }        
        public void setItems(List<Item> items) {
            this.items = items;
        }
    }
    
    // Sample JSON has one recursion of the items list
    String testJson = """
        {"items": [{"items": []}]}
        """;

    /**
     * This test will fail with error:
     * 
     * <pre>
        com.fasterxml.jackson.databind.JsonMappingException: 
          Cannot invoke "com.fasterxml.jackson.databind.JsonDeserializer.getObjectIdReader()" because "valueDes" is null (through reference chain: test.ReproduceTest$Item["items"]->java.util.ArrayList[0]->test.ReproduceTest$Item["items"])
     * </pre>
     */
    @Test
    void testReproduceJackonMapperNPE() throws Exception {
        runTest(new JacksonAnnotationIntrospector() {
            @Override
            public Value findPropertyIgnoralByName(MapperConfig<?> config, Annotated a) {
               return JsonIgnoreProperties.Value.forIgnoredProperties("CanBeAnyValue");
            }
        });
    }

    /**
     * This test will pass without exception when using withAllowSetters()
     */
    @Test
    void testAllowingSettersAvoidsNPE() throws Exception {
        runTest(new JacksonAnnotationIntrospector() {
            @Override
            public Value findPropertyIgnoralByName(MapperConfig<?> config, Annotated a) {
                return JsonIgnoreProperties.Value.forIgnoredProperties("CanBeAnyValue").withAllowSetters();
            }
        });
    }
    
    private void runTest(JacksonAnnotationIntrospector introspector) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setAnnotationIntrospector(introspector);
        Item result = mapper.readValue(testJson, Item.class);
        Assertions.assertEquals(1, result.getItems().size(), 1);
    }
}

Build with Java 17 and mvn clean test

Expected behavior

I thought object should deserialize without hitting NPE without needing to explicitly configure withAllowSetters().

Additional context

No response

@hankolerd hankolerd added the to-evaluate Issue that has been received but not yet evaluated label Mar 6, 2024
@cowtowncoder cowtowncoder added has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue and removed to-evaluate Issue that has been received but not yet evaluated labels Mar 7, 2024
@cowtowncoder
Copy link
Member

cowtowncoder commented Mar 7, 2024

Ok looks like somehow CollectionDeserializer._valueDeserializer is left uninitialized, possibly related to recursive definition and contextualization (but possibly not).
Use of ignored properties will probably construct a new instance of CollectionDeserializer and something is either not copied correctly, or the old instance is referenced somewhere instead of newly created copy.

I don't know of immediate fix but it's good there is a reproduction.

@cowtowncoder
Copy link
Member

cowtowncoder commented Mar 8, 2024

And yes, it certainly looks like it may well be same root cause as #1755.
We do have failing test for that so it affects 2.16/2.17 as well.

cowtowncoder added a commit that referenced this issue Mar 8, 2024
@cowtowncoder
Copy link
Member

Added failing test for this along with one or #1755, failure looks identical fwtw.

@cowtowncoder
Copy link
Member

I think I'll close this one as duplicate: test can remain, but our backlog is so big that I'll try to aggressively prune it.

@cowtowncoder cowtowncoder changed the title NPE deserializing Recursive Structure When Ignoring Properties NPE deserializing Recursive Structure When Ignoring Properties (same as #1755) Mar 10, 2024
@cowtowncoder cowtowncoder added the duplicate Duplicate of an existing (usually earlier) issue label Mar 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate Duplicate of an existing (usually earlier) issue has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Projects
None yet
Development

No branches or pull requests

2 participants