7

I'm trying to serialize a class on which I can't touch anything. The problem is I want to ignore some getWhatever() getter methods, but I can't mark the getter method with @JsonIgnore, as that would mean touching the DTO class.

That serialization is being made from a @RestController method at a Spring REST web service, so it would be great if it could be a solution thinking of that.

I have thought of a solution which I don't like... it would be creating a custom serializer for that DTO class I want to serialize, so I can control what gets serialized and what not, and then, from the @RestController, instead of returning the DTO class (which is what I think is more elegant), returning a String after getting the JSON string with an ObjectMapper and forcing the custom serializer.

I don't like that solution as:

  • I would have to create a custom serializer for every DTO class I need to serialize which has getter methods I dont want to serialize
  • I don't like the solution of returning a string representing a JSON which represents the DTO... I prefer to do that transparently and return the DTO class from the method and let the automation of Spring generate that transformation

Thank you in advance... any help will be very appreciated

EDIT (SOLUTION) I have finally adopted this solution thanks to @Cassio Mazzochi Molin idea:

interface FooMixIn {
    @JsonIgnore
    Object getFoo();
    @JsonIgnore
    Object getBar();
}

@Service
public class ServiceFooSerializer extends SimpleModule{
    public ServiceFooSerializer(){
        this.setMixInAnnotation(Foo.class, FooMixIn.class);
    }
}
kaya3
  • 47,440
  • 4
  • 68
  • 97
Pep Gomez
  • 197
  • 4
  • 14
  • In your RestController instead of having the RequestBody be your Class change it to a `@RequestBody Map args` the key will be the fields normally associated with the object. Next you can just create a new class then populate the fields you want and return it. – locus2k Mar 08 '18 at 13:58

3 Answers3

3

When modifying the classes is not an option, you can use mix-in annotations.

You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment statically defined ones.

First define a mix-in annotation interface (a class would do as well):

public interface FooMixIn {

    @JsonIgnore
    Object getWhatever();
}

Then configure ObjectMapper to use the defined interface as a mix-in for your POJO:

ObjectMapper mapper = new ObjectMapper().addMixIn(Foo.class, FooMixIn.class); 

Some usage considerations:

  • All annotation sets that Jackson recognizes can be mixed in.
  • All kinds of annotations (member method, static method, field, constructor annotations) can be mixed in.
  • Only method (and field) name and signature are used for matching annotations: access definitions (private, protected, ...) and method implementations are ignored.

For more details, have a look at the Jackson documentation.

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • Thank you for your solution... it is the one I have chosen as it is the closest to what I needed. The solution given by @Essex Boy works, but it is a little bit complicated for what I needed and every serialization would go through that serialization... my final solution, using your idea, looks like what I have written in the question (I have edited and put the final code there) – Pep Gomez Mar 09 '18 at 11:48
  • they are effectively the same solution, but I agree this one is cleaner. – Essex Boy Mar 09 '18 at 12:04
1

With some help from this answer and based on the Spring Boot Example

Here is a DTO1 and DTO2 class which look the same (getters and setters not shown) :

public class DTO1 {

    private String property1;
    private String property2;
    private String property3;

Here is a controller for testing:

@RestController
public class HelloController {

    @RequestMapping("/dto1")
    public ResponseEntity<DTO1> dto1() {
        return new ResponseEntity<DTO1>(new DTO1("prop1", "prop2", "prop3"), HttpStatus.OK);
    }

    @RequestMapping("/dto2")
    public ResponseEntity<DTO2> dto2() {
        return new ResponseEntity<DTO2>(new DTO2("prop1", "prop2", "prop3"), HttpStatus.OK);
    }

}

Here is my WebConfig

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Bean
    public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        MyBeanSerializerFactory customSerializationFactory = new MyBeanSerializerFactory(new SerializerFactoryConfig());
        customSerializationFactory.getClasses().add(DTO1.class);
        customSerializationFactory.getFieldsToIgnore().add("property2");
        objectMapper.setSerializerFactory(customSerializationFactory);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jsonConverter.setObjectMapper(objectMapper);
        return jsonConverter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(customJackson2HttpMessageConverter());
    }
}

Here is the custom BeanserializerFactory:

public class MyBeanSerializerFactory extends BeanSerializerFactory {

    private Set<Class> classes = new HashSet<>();
    private Set<String> fieldsToIgnore = new HashSet<>();

    protected MyBeanSerializerFactory(SerializerFactoryConfig config) {
        super(config);
        // TODO Auto-generated constructor stub
    }

    public Set<Class> getClasses() {
        return classes;
    }

    public Set<String> getFieldsToIgnore() {
        return fieldsToIgnore;
    }

    @Override
    protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
        super.processViews(config, builder);

        // ignore fields only for concrete class
        // note, that you can avoid or change this check
        if (classes.contains(builder.getBeanDescription().getBeanClass())) {
            // get original writer
            List<BeanPropertyWriter> originalWriters = builder.getProperties();

            // create actual writers
            List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();

            for (BeanPropertyWriter writer : originalWriters) {
                String propName = writer.getName();
                // if it isn't ignored field, add to actual writers list
                if (!fieldsToIgnore.contains(propName)) {
                    writers.add(writer);
                }
            }

            builder.setProperties(writers);
        }

    }
}

Here is a test which shows property2 removed from DTO1, but not DTO2 :

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void test1() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/dto1").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
                .andExpect(content().string(equalTo("{\"property1\":\"prop1\",\"property3\":\"prop3\"}")));

        mvc.perform(MockMvcRequestBuilders.get("/dto2").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
                .andExpect(content()
                        .string(equalTo("{\"property1\":\"prop1\",\"property2\":\"prop2\",\"property3\":\"prop3\"}")));
    }
}

See https://github.com/gregclinker/json-mapper-example for code above.

Essex Boy
  • 7,565
  • 2
  • 21
  • 24
1

In case, if someone needs to ignore custom getters alone (which do not have setters) MapperFeature.REQUIRE_SETTERS_FOR_GETTERS during the serialization, does the magic.

Example usage:

ObjectMapper objectMapper = JsonMapper.builder().build()
                .configure(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS, true);
MevlütÖzdemir
  • 3,180
  • 1
  • 23
  • 28
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 06 '22 at 17:37