杰克逊JSON,按路径过滤属性 [英] Jackson JSON, filtering properties by path

查看:136
本文介绍了杰克逊JSON,按路径过滤属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在序列化时动态过滤bean属性。

I need to filter bean properties dynamiclly on serialization.

@JsonView 不是一个选项我。

假设我的Bean(作为Json表示法):

Assume my Bean (as Json notation):

{
   id: '1',
   name: 'test',
   children: [
      { id: '1.1', childName: 'Name 1.1' },
      { id: '1.2', childName: 'Name 1.2' }
   ]
}

我想用以下属性编写JSON:

I want to write the JSON with the following properties:

// configure the ObjectMapper to only serialize this properties:
[ "name", "children.childName" ]

预期的JSON结果是:

The expected JSON result is:

{
   name: 'test',
   children: [
      { childName: 'Name 1.1' },
      { childName: 'Name 1.2' }
   ]
}

最后我将创建一个注释( @JsonFilterProperties )以用于Spring i我的RestControllers,如下所示:

Finally I will create an annotation (@JsonFilterProperties) to use with Spring in my RestControllers, something like this:

@JsonFilterProperties({"name", "children.childName"}) // display only this fields
@RequestMapping("/rest/entity")
@ResponseBody
public List<Entity> findAll() {
     return serviceEntity.findAll(); // this will return all fields populated!
}


推荐答案

嗯,这很棘手,但可行。您可以使用Jacksons 过滤器功能( http:// wiki) .fasterxml.com / JacksonFeatureJsonFilter )有一些小的改动。首先,我们将使用类名作为过滤器ID,这样您就不必将 @JsonFIlter 添加到您使用的每个实体:

Well, it's tricky but doable. You can do this using Jacksons Filter feature (http://wiki.fasterxml.com/JacksonFeatureJsonFilter) with some minor alterations. To start, we are going to use class name for filter id, this way you won't have to add @JsonFIlter to every entity you use:

public class CustomIntrospector extends JacksonAnnotationIntrospector {

    @Override
    public Object findFilterId(AnnotatedClass ac) {
        return ac.getRawType();
    }
}

下一步,让超类的过滤器适用到它的所有子类:

Next step, make that filter of super class will apply to all of its subclasses:

public class CustomFilterProvider extends SimpleFilterProvider {

    @Override
    public BeanPropertyFilter findFilter(Object filterId) {
        Class id = (Class) filterId;
        BeanPropertyFilter f = null;
        while (id != Object.class && f == null) {
            f = _filtersById.get(id.getName());
            id = id.getSuperclass();
        }
        // Part from superclass
        if (f == null) {
            f = _defaultFilter;
            if (f == null && _cfgFailOnUnknownId) {
                throw new IllegalArgumentException("No filter configured with id '" + filterId + "' (type " + filterId.getClass().getName() + ")");
            }
        }
        return f;
    }
}

自定义版本 ObjectMapper 利用我们的自定义类:

Custom version of ObjectMapper that utilizes our custom classes:

public class JsonObjectMapper extends ObjectMapper {
    CustomFilterProvider filters;

    public JsonObjectMapper() {
        filters = new CustomFilterProvider();
        filters.setFailOnUnknownId(false);
        this.setFilters(this.filters);
        this.setAnnotationIntrospector(new CustomIntrospector());
    }

    /* You can change methods below as you see fit. */

    public JsonObjectMapper addFilterAllExceptFilter(Class clazz, String... property) {
        filters.addFilter(clazz.getName(), SimpleBeanPropertyFilter.filterOutAllExcept(property));
        return this;
    }

    public JsonObjectMapper addSerializeAllExceptFilter(Class clazz, String... property) {
        filters.addFilter(clazz.getName(), SimpleBeanPropertyFilter.serializeAllExcept(property));
        return this;
    }        

}

现在看看 MappingJackson2HttpMessageConverter ,你会发现它使用了一个 ObjectMapper 的内部,如果你想同时想要不同的配置,你就不能使用它针对不同的要求)。您需要请求范围 ObjectMapper 以及使用它的相应消息转换器:

Now take a look at MappingJackson2HttpMessageConverter, you will see that it uses one instane of ObjectMapper internaly, ergo you cannot use it if you want different configurations simultaneously (for different requests). You need request scoped ObjectMapper and appropriate message converter that uses it:

public abstract class DynamicMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    // Spring will override this method with one that provides request scoped bean
    @Override
    public abstract ObjectMapper getObjectMapper();

    @Override
    public void setObjectMapper(ObjectMapper objectMapper) {
        // We dont need that anymore
    }

    /* Additionally, you need to override all methods that use objectMapper  attribute and change them to use getObjectMapper() method instead */

}

添加一些bean定义:

Add some bean definitions:

<bean id="jsonObjectMapper" class="your.package.name.JsonObjectMapper" scope="request">
    <aop:scoped-proxy/>
</bean>

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="your.package.name.DynamicMappingJacksonHttpMessageConverter">
            <lookup-method name="getObjectMapper" bean="jsonObjectMapper"/>              
        </bean>
    </mvc:message-converters>       
</mvc:annotation-driven>

最后一部分是实现能够检测注释并执行实际配置的内容。为此,您可以创建 @Aspect 。类似于:

And the last part is to implement something that will detect your annotation and perform actual configuration. For that you can create an @Aspect. Something like:

@Aspect
public class JsonResponseConfigurationAspect {

@Autowired
private JsonObjectMapper objectMapper;

@Around("@annotation(jsonFilterProperties)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    /* Here you will have to determine return type and annotation value from jointPoint object. */
    /* See http://stackoverflow.com/questions/2559255/spring-aop-how-to-get-the-annotations-of-the-adviced-method for more info */
    /* If you want to use things like 'children.childName' you will have to  use reflection to determine 'children' type, and so on. */    
}

}

就个人而言,我以不同的方式使用它。我不使用注释,只是手动配置:

Personally, I use this in a different way. I dont use annotations and just do configuration manually:

@Autowired
private JsonObjectMapper objectMapper;

@RequestMapping("/rest/entity")
@ResponseBody
public List<Entity> findAll() {
    objectMapper.addFilterAllExceptFilter(Entity.class, "name", "children"); 
    objectMapper.addFilterAllExceptFilter(EntityChildren.class, "childName"); 
    return serviceEntity.findAll();
}

P.S。这种方法有一个主要缺陷:你不能为一个类添加两个不同的过滤器。

P.S. This approach has one major flaw: you cannot add two different filters for one class.

这篇关于杰克逊JSON,按路径过滤属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆