AntPathPropertyFilter for Jackson2
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
src
.gitignore First implementation without testing Jan 30, 2015
.travis.yml
README.md
pom.xml

README.md

Jackson AntPath Property Filter

A Jackson Filter matching the path of the current value to serialize against the AntPathMatcher. The inclusion / exclusion works similar to the ant file include / exclude functionality. Ant / Maven users should mostly be aware of how this works.

Build Status

Requirements

  • Java 5
  • Jackson 2.5.0+ (2.5.0.RELEASE and its dependencies included)

Installation

For Maven and Maven-compatible dependency managers

Add a dependency to your project with the following co-ordinates:

  • GroupId: ch.mfrey.jackson
  • ArtifactId: jackson-antpathfilter
  • Version: ${jackson-antpathfilter.version}
<dependency>
	<groupId>ch.mfrey.jackson</groupId>
	<artifactId>jackson-antpathfilter</artifactId>
	<version>${jackson-antpathfilter.version}</version>
</dependency>

Usage

String[] filter = new String[] {"*", "*.*", "!not.that.path"};

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Object.class, AntPathFilterMixin.class);

FilterProvider filterProvider = new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter(filter));
objectMapper.setFilters(filterProvider);

objectMapper.writeValueAsString(someObject);

= Inclusion:

"*", "**", "*.*", "someproperty.someNesterProperty.*",

= Exclusion:

"!property", "!**.someExpensiveMethod";

Spring Integration

= Before Spring 4.2.2

For those using this together with Spring it is easy to use the Jackson2Helper class as a Component to configure the objectMapper only once and avoid boilerplate code in each Controller.

<!-- Needed so that Controllers can return a JSON ResponseBody directly as a String result (produced by Jackson2Helper) -->
<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value="application/json; charset=UTF-8" />
            </bean>
        </array>
    </property>
</bean>
@Configuration
public class Jackson2HelperConfig {

    @Bean
    public Jackson2Helper jackson2Helper() {
    	return new Jackson2Helper();
    }
}
@Controller
@RequestMapping(value = "/someObject")
public class SomeController {
    @Autowired
    private Jackson2Helper jackson2Helper;

    @RequestMapping
    @ResponseBody
    public String getSomeObject() {
        return jackson2Helper.writeFiltered(someObject, "*", "*.*", "!not.that.path");
    }
}

= With Spring 4.2.2+

With the latest addon in Spring 4.2.2, the class MappingJacksonValue contains now a function setFilters, it is now possible to integrate this filter in a cleaner way. No specific StringConverter is needed anymore, but we need some more classes for now:

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
    @Override
    public void configureMessageConverters(final List<HttpMessageConverter<?>> messageConverters) {
        // Add a MappingJackson2HttpMessageConverter so that
        // objectMapper.writeFiltered
        // is using the objectMapper configured with the needed Mixin
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
            .json()
            .mixIn(Object.class, AntPathFilterMixin.class)
            .build();
        messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));

        addDefaultHttpMessageConverters(messageConverters);
    }
}
/**
 * Just a helper class to simplify usage
 */
public class AntPathFilterMappingJacksonValue extends MappingJacksonValue {

    public AntPathFilterMappingJacksonValue(final Object value) {
        super(value);
        setFilters(new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter("**")));
    }

    public AntPathFilterMappingJacksonValue(final Object value, final String... filters) {
        super(value);
        setFilters(new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter(filters)));
    }

}
@Controller
@RequestMapping(value = "/someObject")
public class SomeController {
    
    @RequestMapping
    @ResponseBody
    public MappingJacksonValue getSomeObject() {
        return new AntPathFilterMappingJacksonValue(someObject, "*", "*.*", "!not.that.path");
    }
}

Examples (from the Unit Tests)

Data

{
  "address": {
    "streetName":"At my place",
    "streetNumber":"1"
  },
  "email":"somewhere@no.where",
  "firstName":"Martin",
  "lastName":"Frey",
  "manager":{
    "address":null,
    "email":"john.doe@no.where",
    "firstName":"John",
    "lastName":"Doe",
    "manager":null
  }
}
    
Filter: **,
Result: {"address":{"streetName":"At my place","streetNumber":"1"},"email":"somewhere@no.where","firstName":"Martin","lastName":"Frey","manager":{"address":null,"email":"john.doe@no.where","firstName":"John","lastName":"Doe","manager":null}}
Filter: firstName,
Result: {"firstName":"Martin"}
Filter: **,!manager,
Result: {"address":{"streetName":"At my place","streetNumber":"1"},"email":"somewhere@no.where","firstName":"Martin","lastName":"Frey"}
Filter: manager,manager.firstName,manager.lastName,
Result: {"manager":{"firstName":"John","lastName":"Doe"}}
Filter: *,address.*,manager.firstName,
Result: {"address":{"streetName":"At my place","streetNumber":"1"},"email":"somewhere@no.where","firstName":"Martin","lastName":"Frey","manager":{"firstName":"John"}}
Filter: **,-manager,!**.streetNumber,
Result: {"address":{"streetName":"At my place"},"email":"somewhere@no.where","firstName":"Martin","lastName":"Frey"}
Filter: "reports", "reports.firstName"
Result: {"reports":[{"firstName":"First 0"},{"firstName":"First 1"},{"firstName":"First 2"},{"firstName":"First 3"},{"firstName":"First 4"},{"firstName":"First 5"},{"firstName":"First 6"},{"firstName":"First 7"},{"firstName":"First 8"},{"firstName":"First 9"}]}