skip to Main Content

I have a Spring AOP service which intercepts a lot of different third-party controllers endpoints. My service runs an endpoint, gets the result DTO, serialize DTO to json with ObjectMapper, sends json to Kafka and return the DTO outside. The problem is: the service shouldn’t serialise fields of particular types (e.g MultipartFile). I can’t use @JsonIgnore annotation, because DTO should be returned outside without changes.
Can I make ObjectMapper skip fields of certain types? Or, maybe, copy the DTO and set this fields to null with reflection?

3

Answers


  1. Chosen as BEST ANSWER

    I have changeable list of prohibited classes in YAML properties file, so I can't use mixins. The task was solved in this way:

    @Value("${classes-to-remove}")
    private String classesToRemove;
    
    public String getClearedJson(MyDTO myDTO) {
        List<String> fieldNamesToRemove = new ArrayList<>();
    
        for (Field field: myDTO.getClass().getDeclaredFields()) {
            if (classesToRemove.contains(field.getType().getSimpleName())) {
                fieldNamesToRemove.add(field.getName());
                continue;
            }
    
            if (field.getGenericType() instanceof ParameterizedType) {
                ParameterizedType listType = (ParameterizedType) field.getGenericType();
                Class<?> listClass = (Class<?>) listType.getActualTypeArguments()[0];
                if (classesToRemove.contains(listClass.getSimpleName())) {
                    fieldNamesToRemove.add(field.getName());
                }
            }
        }
    
        String json = objectMapper.writeValueAsString(myDTO);
        JsonNode jsonNode = objectMapper.readTree(json);
        ObjectNode object = (ObjectNode) jsonNode;
        fieldNamesToRemove.forEach(object::remove);
        String updatedJson = objectMapper.writeValueAsString(object);
        
        return updatedJson;
    }
    

  2. Always remember that Jackson is literal magic and is endlessly customizable 🙂

    There are many ways to achieve what you want. Mixins are an easy one.

    Mix-ins

    Let’s suppose you have the following DTO class and want to exclude File:

    public class SimpleDTO {
    
        private final File file;
        private final String name;
    
        @JsonCreator
        public SimpleDTO(@JsonProperty("file") File file, @JsonProperty("name") String name) {
            this.file = file;
            this.name = name;
        }
    
        public File getFile() {
            return file;
        }
    
        public String getName() {
            return name;
        }
    }
    

    You can create a mix-in such as:

    @JsonIgnoreType
    public interface FileMixin {}
    

    And then use it when serializing:

    //Pretend the File class was annotated by @JsonIgnoreType
    ObjectMapper mapper = new ObjectMapper().addMixIn(File.class, FileMixin.class);
    
    String serialized = mapper.writeValueAsString(new SimpleDTO(new File("/tmp/simple.txt"), "A kewl name"));
    System.out.println(serialized); //Prints {"name":"A kewl name"}
    

    Instead of @JsonIgnoreType, you could have also mixed-in @JsonIgnore to the file field in SimpleDTO or @JsonIgnoreProperties(value = { "file" }) or any other Jackson annotation. Here’s an overview of annotations used to ignore fields.

    Note: Do not create a new ObjectMapper each time. Configure one instance for serialization and share it forever.

    Programmatic configuration

    Mix-ins let you add annotations from the outside, but annotations are still static configuration. If you have to dynamically choose what to ignore, annotations, mixed-in or not, won’t suffice. Jackson, being magic and all, lets you achieve programmatically anything that can be done via annotations:

    public class FieldFilteringIntrospector extends NopAnnotationIntrospector {
    
        private final Set<Class<?>> ignoredTypes;
    
        public FieldFilteringIntrospector(Set<Class<?>> ignoredTypes) {
            this.ignoredTypes = Collections.unmodifiableSet(ignoredTypes);
        }
    
        public FieldFilteringIntrospector(Class<?>... ignoredTypes) {
            this.ignoredTypes = Arrays.stream(ignoredTypes).collect(Collectors.toUnmodifiableSet());
        }
    
        @Override
        public Boolean isIgnorableType(AnnotatedClass ac) {
            return ignoredTypes.contains(ac.getRawType());
        }
    }
    

    And then use it when serializing:

    SimpleModule module = new SimpleModule("type-filter-module") {
        @Override
        public void setupModule(SetupContext context) {
            super.setupModule(context);
            context.insertAnnotationIntrospector(new FieldFilteringIntrospector(File.class));
        }
    };
    ObjectMapper mapper = new ObjectMapper().registerModule(module);
    
    String serialized = mapper.writeValueAsString(new SimpleDTO(new File("/tmp/simple.txt"), "A kewl name"));
    System.out.println(serialized); //Prints {"name":"A kewl name"}
    

    By overriding other methods from NopAnnotationIntrospector, you can emulate other annotations, @JsonIgnore and others.

    Note: Again, create an ObjectMapper only once.

    Login or Signup to reply.
  3. you can use an object mapper to overcome this or can be used getDeclaredFields method to build a logic to remove unwanted attributes and return

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search