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

Problem using optional ObjectIds and JacksonInject in the constructor when deserializing #1643

Open
StefanVanDyck opened this issue Jun 4, 2017 · 3 comments

Comments

@StefanVanDyck
Copy link

I'm having some issues with using @JacksonInject inside the constructor in combination with optional ObjectIds.
I'm trying to deserialize objects that not always have an ObjectId defined.
This works fine, but as soon as I use @JacksonInject to inject a constructor argument, I receive a
com.fasterxml.jackson.databind.exc.MismatchedInputException: No Object Id found for an instance of test.jackson.SimpleTest$Simple, to assign to property 'id

Is this an issue or is this by design? I must confess I'm still new to the jackson library.
I've tried several version, mostly 2.8.8 but 2.9.0.pr3 also shows the same behavior.

I've includes a small test to illustrate my problem.

package test.jackson;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;

public class SimpleTest {

    private ObjectMapper mapper;
    private Simple injectingObject;

    @Before
    public void setUp() {
        InjectableValues injectableValues = new InjectableValues.Std()
                .addValue(String.class, "Injected");
        mapper = new ObjectMapper().setInjectableValues(injectableValues);
    }

    @Test
    public void deserialize_GivenSimpleObjectWithObjectId() throws IOException {
        Simple simple = mapper.readValue("{ \"id\": 1  }", Simple.class);
        assertThat(simple.getMessage()).isEqualTo("Injected");
    }

    @Test // FAILS
    public void deserialize_GivenSimpleObjectWithoutObjectId() throws IOException {
        Simple simple = mapper.readValue("{   }", Simple.class);
        assertThat(simple.getMessage()).isEqualTo("Injected");
    }

    @Test
    public void deserialize_SimpleWithoutConstructorInjectionObjectWithObjectId() throws IOException {
        SimpleWithoutConstructorInjection simple = mapper.readValue("{ \"id\": 1  }", SimpleWithoutConstructorInjection.class);
        assertThat(simple.getMessage()).isEqualTo("Injected");
    }

    @Test
    public void deserialize_SimpleWithoutConstructorInjectionObjectWithoutObjectId() throws IOException {
        SimpleWithoutConstructorInjection simple = mapper.readValue("{   }", SimpleWithoutConstructorInjection.class);
        assertThat(simple.getMessage()).isEqualTo("Injected");
    }

    @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
    public static class Simple {
        private int id;
        private String message;

        public Simple(@JacksonInject String message) {
            this.message = message;
        }
        public String getMessage() {
            return message;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

    @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
    public static class SimpleWithoutConstructorInjection {
        private int id;
        private String message;

        @JacksonInject
        public void setMessage(String message) {
            this.message = message;
        }

        public String getMessage() {
            return message;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }
}
@StefanVanDyck
Copy link
Author

I managed to resolve this issue by doing a very ugly hack, that gets an auto generated objectId (getName) if the id is not found as an external property. And using reflection to set the field used to store it in the ValueInstantiator.
I assume that this is a very bad idea...

public class NamedTestObjectInstantiator extends StdValueInstantiator {

    public NamedTestObjectInstantiator(StdValueInstantiator src) {
        super(src);
    }

    @Override
    public Object createFromObjectWith(DeserializationContext ctxt, SettableBeanProperty[] props, PropertyValueBuffer buffer) throws IOException {
        NamedTestObject object = (NamedTestObject)super.createFromObjectWith(ctxt, props, buffer);
        forcefullySetObjectId(buffer, object.getName());
        return object;
    }

    private void forcefullySetObjectId(PropertyValueBuffer buffer, String name) {
        try {
            Field idField = buffer.getClass().getDeclaredField("_idValue");
            idField.setAccessible(true);
            Object idValue = idField.get(buffer);
            if(idValue == null) {
                idField.set(buffer, name);
            }
            idField.setAccessible(false);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

}

@cowtowncoder
Copy link
Member

On original question/problem: Object Ids are not optional -- if object type is defined to use Object Id / Reference constructs, Id must be provided. It is not optional.

But there is an RFE, I think, to allow relaxing this limit, so perhaps in future this could be made to work. I do not remember issue number off-hand.
This would just mean that such an instance could not be referred via reference, but would otherwise be allowed to be deserialized.

@DidierLoiseau
Copy link

FYI the RFE you are referring to is #1698.

In my case I am trying to unify the ObjectMapper across my application, but some services are using object id's and others are not, but they share part of their model.

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

3 participants