FasterXML/jackson-module-afterburner

Cannot read some "pretty" documents

lehcim opened this issue · 6 comments

I'm facing an issue reading a pretty-printed document from a file with afterburner 2.6.2.
The input is a valid JSON document, but deserialization fails with a message saying that a colon is not found:

com.fasterxml.jackson.core.JsonParseException: Unexpected character ('[' (code 91)): was expecting a colon to separate field name and value
 at [Source: foo.json; line: 3, column: 6]

The document is as follow (please note the space before the colon):

{
    "foos" :
    [
    ]
}

and the java code looks like this :

class FooList {
    public List<Object> foos ;
}

@Test
public void testBurnerFile() throws IOException {
    ObjectMapper burner = new ObjectMapper().registerModule(new AfterburnerModule()) ;
    burner.readValue(filename, FooList.class) ;
}

Using a raw ObjectMapper, there is no error reading a value from any source (String, File, Reader, Stream). However, after registering the AfterburnerModule, the deserialization fails reading from a File or a Stream (but other sources are ok...)

If you edit the document and remove the space before the colon, then everything is fine with Afterburner. Of course, this is only feasible for testing purpose, in real life I can't change the formatting on the server producing the JSON document...

Here is the full stack trace:

com.fasterxml.jackson.core.JsonParseException: Unexpected character ('[' (code 91)): was expecting a colon to separate field name and value
 at [Source: foo.json; line: 3, column: 6]
        at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1581)
        at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:533)
        at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:462)
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._skipColon2(UTF8StreamJsonParser.java:3006)
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._skipColonFast(UTF8StreamJsonParser.java:1104)
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextFieldName(UTF8StreamJsonParser.java:937)
        at com.fasterxml.jackson.module.afterburner.deser.SuperSonicBeanDeserializer.deserialize(SuperSonicBeanDeserializer.java:147)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3731)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2618)
        at afterburner.AfterburnerTest.testBurnerFile(AfterburnerTest.java:70)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

Here is the complete repro Junit test:

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;

class FooList {
    public List<Object> foos ;
}

public class AfterburnerIssue60Test {

        String content = 
        "{\n" +
        "    \"foos\" :\n" + 
        "    [\n" + 
        "    ]\n" +
        "}";
        File filename = new File("foo.json") ;

        @Before
        public void setup()  throws IOException {
            try (FileWriter writer = new FileWriter(filename)) {
                writer.write(content);
            }
        }

        @Test
        public void testMapperString() throws IOException {
            ObjectMapper mapper = new ObjectMapper() ;
            mapper.readValue(content, FooList.class) ;
        }

        @Test
        public void testMapperFile() throws IOException {
            ObjectMapper mapper = new ObjectMapper() ;
            mapper.readValue(filename, FooList.class) ;
        }

        @Test
        public void testMapperReader() throws IOException {
            ObjectMapper mapper = new ObjectMapper() ;
            mapper.readValue(new StringReader(content), FooList.class) ;
        }

        @Test
        public void testMapperStream() throws IOException {
            ObjectMapper mapper = new ObjectMapper() ;
            mapper.readValue(new ByteArrayInputStream(content.getBytes()), FooList.class) ;
        }

        @Test
        public void testBurnerString() throws IOException {
            ObjectMapper burner = new ObjectMapper().registerModule(new AfterburnerModule()) ;
            burner.readValue(content, FooList.class) ;
        }

        @Test
        public void testBurnerFile() throws IOException {
            ObjectMapper burner = new ObjectMapper().registerModule(new AfterburnerModule()) ;
            burner.readValue(filename, FooList.class) ;
        }

        @Test
        public void testBurnerReader() throws IOException {
            ObjectMapper burner = new ObjectMapper().registerModule(new AfterburnerModule()) ;          
            burner.readValue(new StringReader(content), FooList.class) ;
        }

        @Test
        public void testBurnerStream() throws IOException {
            ObjectMapper burner = new ObjectMapper().registerModule(new AfterburnerModule()) ;
            burner.readValue(new ByteArrayInputStream(content.getBytes()), FooList.class) ;
        }       
}

and the results:

Tests in error:
  testBurnerFile(afterburner.AfterburnerIssue60Test): Unexpected character ('[' (code 91)): was expecting a colon to separate field name and value
  testBurnerStream(afterburner.AfterburnerIssue60Test): Unexpected character ('[' (code 91)): was expecting a colon to separate field name and value

Tests run: 8, Failures: 0, Errors: 2, Skipped: 0

Hope this helps...

Very interesting... thank you for reporting this, with a unit test.

I suspect this is due to Afterburner calling slightly different parsing methods, and the actual issue being within JSON parser (probably within nextFieldName(SerializableString) implementation).
I should be able to get this fixed for 2.6.3.

Yes, I tried to understand the problem using the debugger, but the solution was not obvious for me ...
Thank you for supporting this issue.

I can reproduce the problem locally, and it does indeed only affect byte-backed parser, which explain why some other sources (like String) would not exhibit it. Codepaths are optimized quite differently.

Thank you very much !

Confirmed : 2.6.3 release solves my real-life use case.
Thank you again.