Search code examples
jacksonjerseyjax-rsjackson2

hide Jackson fields based on costume dynamic criteria for JaxRS Respose


Idea is simple. I have a object and I would like to hide some fields based on the some specific roles.

I have roles in the system "dog", "cat" etc.

class Food{

  String name;

  @HideInfoForTheRoles({"dog", "cat"})
  String age;
}

So I think to create something like that:

public String hideForRole(T object, String role){
// return new json
}

Or maybe I can override some denationalization method to force Jackson to hide field based on my annotation?


Solution

  • Create annotation that supports on FIELD and METHOD

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.METHOD})
    public @interface HideFor{
        String[] roles() default{};
    }
    

    and logic that supports annotation for both field and methods

    public class AccessRestrictionFilter extends SimpleBeanPropertyFilter {
    
    
        @Override
        public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
                throws Exception {
    
            if(writer.getAnnotation(HideFor.class)!=null && isHidable( Arrays.asList(writer.getAnnotation(HideFor.class).roles()))){
                logger.debug("Found restriction on the getter method of the field: " + pojo + " Restriction For" + Arrays.toString(writer.getAnnotation(HideFor.class).roles()) );
                     return;
            }
    
            Field[] fields = jgen.getCurrentValue().getClass().getDeclaredFields();
    
            Optional<Field> field = Arrays.stream(fields)
                    .filter(f-> f.getName().equalsIgnoreCase(writer.getName())).findAny();
    
            if(field.isPresent() && field.get().getAnnotation(HideFor.class)!=null){
                if(isHidable( Arrays.asList(writer.getAnnotation(HideFor.class).roles()))){
                    System.out.println("Found restriction on the field " + field.get().getName() + " Restriction For " + Arrays.toString(writer.getAnnotation(HideFor.class).roles()));
                    return;
                }
            }
            writer.serializeAsField(pojo, jgen, provider);
        }
    
        private boolean isHidable(List<String> rolesToHide){ // imlement the logic // }
    
    }
    

    Usage:

        FilterProvider filterProvider = new SimpleFilterProvider().addFilter("AccessRestrictionFilter", new AccessRestrictionFilter()); 
        new ObjectMapper().writer(filterProvider ).writeValueAsString(myObjToFilter);
    

    I use Jersey/Spring and my configuration looks like this:

    @Provider
    @Produces({MediaType.APPLICATION_JSON})
    public class JacksonJsonProvider extends JacksonJaxbJsonProvider {
        public JacksonJsonProvider(AccessRestrictionFilter filter) {
            ObjectMapper objectMapper = new ObjectMapper()
                    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                    .setFilterProvider(new SimpleFilterProvider().addFilter("AccessRestriction", filter));
            setMapper(objectMapper);
        }
    }
    

    And Filter:

       @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
        @Bean("accessRestrictionFilter")
        public AccessRestrictionFilter accessRestrictionFilter(){
              return new AccessRestrictionFilter();
        }
    

    Note: in the filter I use the Security Context, because of this scope of the filter is Session (Not to share the state but create new object for each user)

    and that's my POJO:

    @JsonFilter("AccessRestrictionFilter")
    public class MyClass {
    
        @HideFor(roles = {"ROLE_USER", "ROLE_EDITOR"})
        private int val;