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

Removing "type" attribute with Mixin not taken in account if using ObjectMapper.copy() #1998

Closed
SBKila opened this issue Apr 12, 2018 · 3 comments
Milestone

Comments

@SBKila
Copy link

SBKila commented Apr 12, 2018

Hi,

Objectif

I'd like to remove several attributes and in particular type from serialization output

What I'm doing

I'm using Mixin concept to remove the attribute 'type' like follow.
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.EXISTING_PROPERTY)

I have a static ObjectMapper created with all my default settings and I clone it .copy(); each time I need to use a new one.

  1. When only one object is used all perfectly working. testA_OK
  2. But if I use more than one mapper, Mixin are taken in account expect @JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.EXISTING_PROPERTY) testB_OK
  3. If I replace ObjectMapper.copy() by a new ObjectMapper(), all perfectly working testC_OK

JUnit Test class

`
public class ObjectMapperCopyTest {

private static final ObjectMapper DEFAULT_MAPPER;
final static String FULLMODEL="{\"format\":\"1.0\",\"child\":{\"type\":\"CHILD_B\",\"name\":\"testB\"},\"notVisible\":\"should not be present\"}";
final static String EXPECTED="{\"format\":\"1.0\",\"child\":{\"name\":\"testB\"}}";
static {
    DEFAULT_MAPPER = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
            .configure(JsonParser.Feature.ALLOW_COMMENTS, true)
            .registerModule(new JavaTimeModule())
            .findAndRegisterModules();
}

public class MyModelView {
}

interface MixinConfig {
    interface MyModelRoot {
        @JsonView(MyModelView.class)
        public String getFormat();

        @JsonView(MyModelView.class)
        public MyModelChildBase getChild();
    }

    @JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.EXISTING_PROPERTY) interface MyModelChildBase {
        @JsonView(MyModelView.class)
        public String getName();
    }

}

class MyModelRoot {
    @JsonProperty
    @NotNull
    private String format = "1.0";

    public String getFormat() {
        return format;
    }
    @JsonProperty
    private MyModelChildBase child;

    public MyModelChildBase getChild() {
        return child;
    }

    public void setChild(MyModelChildBase child) {
        this.child = child;
    }

    @JsonProperty
    @NotNull
    private String notVisible = "should not be present";

    public String getNotVisible() {
        return notVisible;
    }
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = MyChildA.class, name = "CHILD_A")
        ,
        @JsonSubTypes.Type(value = MyChildB.class, name = "CHILD_B")
})
abstract class MyModelChildBase {
    @JsonProperty
    private String name;

    public String getName() {
        return name;
    }

    @JsonIgnore
    public void setName(String name) {
        this.name = name;
    }
}

class MyChildA extends MyModelChildBase {
    public MyChildA(String name) {
        setName(name);
    }

}

class MyChildB extends MyModelChildBase {
    public MyChildB(String name) {
        setName(name);
    }
}

@Test
public void testA_OK() {

    MyModelRoot myModelInstance = new MyModelRoot();
    myModelInstance.setChild(new MyChildB("testB"));

    ObjectMapper myObjectMapper = DEFAULT_MAPPER.copy();

    myObjectMapper.addMixIn(MyModelRoot.class, MixinConfig.MyModelRoot.class)
            .addMixIn(MyModelChildBase.class, MixinConfig.MyModelChildBase.class)
            .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
            .setConfig(myObjectMapper.getSerializationConfig().withView(MyModelView.class));

    String result = getString(myModelInstance, myObjectMapper);
    System.out.println("result: "+result);
    Assert.assertEquals(EXPECTED, result);

}


@Test
public void testB_KO() {

    MyModelRoot myModelInstance = new MyModelRoot();
    myModelInstance.setChild(new MyChildB("testB"));

    ObjectMapper myObjectMapper = DEFAULT_MAPPER.copy();

    String postResult = getString(myModelInstance, myObjectMapper);
    Assert.assertEquals(FULLMODEL, postResult);
    System.out.println("postResult: "+postResult);

    myObjectMapper = DEFAULT_MAPPER.copy();
    myObjectMapper.addMixIn(MyModelRoot.class, MixinConfig.MyModelRoot.class)
            .addMixIn(MyModelChildBase.class, MixinConfig.MyModelChildBase.class)
            .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
            .setConfig(myObjectMapper.getSerializationConfig().withView(MyModelView.class));

    String result = getString(myModelInstance, myObjectMapper);
    System.out.println("result: "+result);
    Assert.assertEquals(EXPECTED, result);

}

@Test
public void testC_OK() {

    MyModelRoot myModelInstance = new MyModelRoot();
    myModelInstance.setChild(new MyChildB("testB"));

    ObjectMapper myObjectMapper = getObjectMapper();

    String postResult = getString(myModelInstance, myObjectMapper);
    Assert.assertEquals(FULLMODEL, postResult);
    System.out.println("postResult: "+postResult);

    myObjectMapper = getObjectMapper();

    myObjectMapper.addMixIn(MyModelRoot.class, MixinConfig.MyModelRoot.class)
            .addMixIn(MyModelChildBase.class, MixinConfig.MyModelChildBase.class)
            .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
            .setConfig(myObjectMapper.getSerializationConfig().withView(MyModelView.class));

    String result = getString(myModelInstance, myObjectMapper);
    System.out.println("result: "+result);
    Assert.assertEquals(EXPECTED, result);

}

private ObjectMapper getObjectMapper() {
    return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
            .configure(JsonParser.Feature.ALLOW_COMMENTS, true)
            .registerModule(new JavaTimeModule())
            .findAndRegisterModules();
}

private String getString(MyModelRoot myModelInstance, ObjectMapper myObjectMapper) {
    CharArrayWriter writer = new CharArrayWriter();
    try {
        myObjectMapper.writerFor(MyModelRoot.class).writeValue(writer, myModelInstance);

    } catch (IOException e) {
        e.printStackTrace();
    }
    return writer.toString();
}

}
`

@SBKila SBKila changed the title Removing "type" attribute with Mixin not taken in account if using ObjectMapper.clone() Removing "type" attribute with Mixin not taken in account if using ObjectMapper.copy() Apr 12, 2018
@cowtowncoder
Copy link
Member

Ok. Which version?

@cowtowncoder cowtowncoder added 2.9 and removed ACTIVE labels Apr 18, 2018
@cowtowncoder cowtowncoder added this to the 2.9.6 milestone Apr 19, 2018
@cowtowncoder
Copy link
Member

Reproducible on 2.9.5. Digging deep into code, looks like cache in ClassIntrospector ends up being shared, leading to previously resolved class metadata (annotations, including mix-in ones) getting reused accidentally.

@cowtowncoder
Copy link
Member

Note to self: fixed for 2.9.6 but still problematic on master (for 3.0.0 snapshot) -- points to new challenges wrt builder.

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