Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

JsonMappingException: Invalid field index when using mixin annotations on and off #51

Closed
natnan opened this issue Feb 25, 2015 · 9 comments
Milestone

Comments

@natnan
Copy link

natnan commented Feb 25, 2015

Using 2.5.0 via dropwizard 0.8-rc2.

In one of the use cases I'm implementing I need to ignore one of the fields when I'm serializing/deserializing an object while in general I don't need to do so. So, in that particular class I use a copy of the main object mapper with mixin configuration.

this.objectMapper = objectMapper.copy();
this.objectMapper.addMixIn(MyClass.class, IgnoreParticularFieldMixIn.class);

...
public abstract class IgnoreParticularFieldMixIn{
  @JsonIgnore
  public abstract byte[] getParticularField();
}

First, an object is serialized with mixIn, then without it. But the second operation fails with JsonMappingException: Invalid field index (valid; 0 <= n < 4): 4 (because the ignored field is the 5th field). So it looks like MyClass' details are cached with 4 fields in the beginning, but encountered 5 fields on the second which led to an exception.

I understand the logic there but the issue here is that these two operations are handled by two different object mappers, so to me, they should have two different caches. But it looks like afterburner is keeping them in a static context. Is this how it's designed or a bug?

@cowtowncoder
Copy link
Member

It sure sounds like a bug: you should not have to worry about details like caching.

Would it be possible to complete the code snippet to a unit test? I would like to fix this; and it could be something relatively simple.

@natnan
Copy link
Author

natnan commented Feb 25, 2015

Well, I've just written a unit test but it doesn't throw the same exception. It could be something related though. The original exception I got from my application could be due to a certain flow of objectmapper usage as it is used in various contexts within a Jersey before it crashes. It is fairly difficult for me to filter the flow down into a unit test unfortunately. Also having about a dozen other modules might also have helped for which I tried adding into the unit test but didn't change anything.

So let's work with the new exception first then we can go back to Invalid field indexif that doesn't help.

Unit Test

public class SampleObject {
  private String field1;
  private int field2;
  private byte[] field3;

  public SampleObject(String field1, int field2, byte[] field3) {
    this.field1 = field1;
    this.field2 = field2;
    this.field3 = field3;
  }

  public String getField1() {
    return field1;
  }

  public void setField1(String field1) {
    this.field1 = field1;
  }

  public int getField2() {
    return field2;
  }

  public void setField2(int field2) {
    this.field2 = field2;
  }

  public byte[] getField3() {
    return field3;
  }

  public void setField3(byte[] field3) {
    this.field3 = field3;
  }
}

public abstract class IgnoreField3MixIn {
  @JsonIgnore
  public abstract byte[] getField3();
}

public class MixinTest {

  @Test
  public void test() throws JsonProcessingException {
    SampleObject sampleObject = new SampleObject("field1", 2, "field3".getBytes());

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new AfterburnerModule());

    ObjectMapper objectMapperCopy = objectMapper.copy();
    objectMapperCopy.addMixIn(SampleObject.class, IgnoreField3MixIn.class);

    objectMapperCopy.writeValueAsString(sampleObject);

    objectMapper.writeValueAsString(sampleObject);
  }

}

Exception

com.fasterxml.jackson.databind.JsonMappingException: No objectGetters defined (through reference chain: com.afterburner.test.SampleObject["field3"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:210)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:177)
    at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:190)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:671)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:129)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3385)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2779)
    at com.afterburner.test.MixinTest.test(MixinTest.java:52)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.UnsupportedOperationException: No objectGetters defined
    at com.fasterxml.jackson.module.afterburner.ser.BeanPropertyAccessor.objectGetter(BeanPropertyAccessor.java:24)
    at com.fasterxml.jackson.module.afterburner.ser.ObjectMethodPropertyWriter.serializeAsField(ObjectMethodPropertyWriter.java:43)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:663)
    ... 31 more

@cowtowncoder
Copy link
Member

Thanks! It is not surprising that it could be tricky to reproduce.

@cowtowncoder
Copy link
Member

I have one guess as to why this might be happening. The problem would be that although copy() does indeed clean up all the state, one thing it can not do is unload Afterburner-generated access classes.
This means that when copy of ObjectMapper tries to generate access classes again, it finds classes already defined (since naming method for classes hasn't changed).

As to how to resolve this... maybe addition of checksum on accessor name (based on bytecode) would make it possible to avoid this name conflict.

@natnan
Copy link
Author

natnan commented Mar 3, 2015

I had thought of that, and tried creating a new ObjectMapper instead of copying as well; the result was the same.

@cowtowncoder
Copy link
Member

Right, that makes sense -- class definitions are not owned by ObjectMapper, or JVM. Owner is ClassLoader. So the only existing work-around would be to create a different ClassLoader actually.
But I'll see if I can improve naming to avoid collisions instead.

@cowtowncoder
Copy link
Member

Rather big internal changes needed, since there's a chicken-and-egg problem with including checksum in class name (need to generate class to calculate checksum; but class name is embedded in bytecode itself!). Which is why this will unfortunately need to go in 2.6.0. But it is fixed as far as I can see, and should cover many other similar possible problem cases.

@natnan
Copy link
Author

natnan commented Mar 4, 2015

Great, thanks!

@cowtowncoder cowtowncoder modified the milestones: 2.6., 2.2.1 Mar 4, 2015
@ailieff
Copy link

ailieff commented Jun 30, 2015

I just came across this issue, and for people that cant find a viable solution, and are stuck to older jackson version here is a temporary workaround that seems to work:

// just before you setup ur mixings, ask Jackson to cache the regular stuff - use a new objectmapper!
ObjectMapper objectMapperFix = Jackson.newObjectMapper();
objectMapperFix.canSerialize(YourClass.class);

// then use your regular code, with NON-STATIC mapper object
mapper.addMixInAnnotations(YourClass.class, YourClassMixIn.class);

@cowtowncoder cowtowncoder modified the milestones: 2.2.1, 2.6.0 Jun 30, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants