恕我直言,最简单的方法是使用自省动态生成包含选定字段的哈希,然后使用 Json 序列化该哈希。您只需决定可用字段的列表是什么(见下文)。
这里有两个示例函数可以做到这一点,第一个获取所有公共字段和公共 getter,第二个获取当前类及其所有父类中的所有声明字段(包括私有字段):
public Map<String, Object> getPublicMap(Object obj, List<String> names)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<String> gettedFields = new ArrayList<String>();
Map<String, Object> values = new HashMap<String, Object>();
for (Method getter: obj.getClass().getMethods()) {
if (getter.getName().startsWith("get") && (getter.getName().length > 3)) {
String name0 = getter.getName().substring(3);
String name = name0.substring(0, 1).toLowerCase().concat(name0.substring(1));
gettedFields.add(name);
if ((names == null) || names.isEmpty() || names.contains(name)) {
values.put(name, getter.invoke(obj));
}
}
}
for (Field field: obj.getClass().getFields()) {
String name = field.getName();
if ((! gettedFields.contains(name)) && ((names == null) || names.isEmpty() || names.contains(name))) {
values.put(name, field.get(obj));
}
}
return values;
}
public Map<String, Object> getFieldMap(Object obj, List<String> names)
throws IllegalArgumentException, IllegalAccessException {
Map<String, Object> values = new HashMap<String, Object>();
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
for (Field field : clazz.getDeclaredFields()) {
String name = field.getName();
if ((names == null) || names.isEmpty() || names.contains(name)) {
field.setAccessible(true);
values.put(name, field.get(obj));
}
}
}
return values;
}
然后你只需要得到这个函数之一的结果(或者你可以适应你的要求的一个)并用 Jackson 序列化它。
如果您对域对象进行了自定义编码,则必须在两个不同的地方维护序列化规则:散列生成和杰克逊序列化。在这种情况下,您可以简单地使用 Jackson 生成完整的类序列化,然后过滤生成的字符串。这是一个这样的过滤函数的例子:
public String jsonSub(String json, List<String> names) throws IOException {
if ((names == null) || names.isEmpty()) {
return json;
}
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(json, HashMap.class);
for (String name: map.keySet()) {
if (! names.contains(name)) {
map.remove(name);
}
}
return mapper.writeValueAsString(map);
}
编辑:Spring MVC 中的集成
当您谈到 Web 服务和 Jackson 时,我假设您使用 Spring RestController 或 ResponseBody 注释和(在后台)MappingJackson2HttpMessageConverter。如果你改用 Jackson 1,它应该是 MappingJacksonHttpMessageConverter。
我的建议只是添加一个新的HttpMessageConverter,它可以利用上述过滤功能之一,并将实际工作(以及辅助方法)委托给真正的MappingJackson2HttpMessageConverter。在该新转换器的write 方法中,可以访问最终的fields 请求参数,而无需显式的ThreadLocal 变量,这要归功于Spring RequestContextHolder。那样:
- 您可以明确区分角色,而无需修改现有控制器
- 您没有对 Jackson2 配置进行任何修改
- 您不需要新的 ThreadLocal 变量,只需在已经绑定到 Spring 的类中使用 Spring 类,因为它实现了
HttpMessageConverter
这是一个这样的消息转换器的例子:
public class JsonConverter implements HttpMessageConverter<Object> {
private static final Logger logger = LoggerFactory.getLogger(JsonConverter.class);
// a real message converter that will respond to ancilliary methods and do the actual work
private HttpMessageConverter<Object> delegate =
new MappingJackson2HttpMessageConverter();
// allow configuration of the fields name
private String fieldsParam = "fields";
public void setFieldsParam(String fieldsParam) {
this.fieldsParam = fieldsParam;
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegate.canRead(clazz, mediaType);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegate.canWrite(clazz, mediaType);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return delegate.getSupportedMediaTypes();
}
@Override
public Object read(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return delegate.read(clazz, inputMessage);
}
@Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// is there a fields parameter in request
String[] fields = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest().getParameterValues(fieldsParam);
if (fields != null && fields.length != 0) {
// get required field names
List<String> names = new ArrayList<String>();
for (String field : fields) {
String[] f_names = field.split("\\s*,\\s*");
names.addAll(Arrays.asList(f_names));
}
// special management for Map ...
if (t instanceof Map) {
Map<?, ?> tmap = (Map<?, ?>) t;
Map<String, Object> map = new LinkedHashMap<String, Object>();
for (Entry entry : tmap.entrySet()) {
String name = entry.getKey().toString();
if (names.contains(name)) {
map.put(name, entry.getValue());
}
}
t = map;
} else {
try {
Map<String, Object> map = getMap(t, names);
t = map;
} catch (Exception ex) {
throw new HttpMessageNotWritableException("Error in field extraction", ex);
}
}
}
delegate.write(t, contentType, outputMessage);
}
/**
* Create a Map by keeping only some fields of an object
* @param obj the Object
* @param names names of the fields to keep in result Map
* @return a map containing only requires fields and their value
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static Map<String, Object> getMap(Object obj, List<String> names)
throws IllegalArgumentException, IllegalAccessException {
Map<String, Object> values = new HashMap<String, Object>();
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
for (Field field : clazz.getDeclaredFields()) {
String name = field.getName();
if (names.contains(name)) {
field.setAccessible(true);
values.put(name, field.get(obj));
}
}
}
return values;
}
}
如果你想让转换器更通用,你可以定义一个接口
public interface FieldsFilter {
Map<String, Object> getMap(Object obj, List<String> names)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}
并用它的实现注入它。
现在您必须要求 Spring MVC 使用该自定义消息控制器。
如果您使用 XML 配置,您只需在 <mvc:annotation-driven> 元素中声明它:
<mvc:annotation-driven >
<mvc:message-converters>
<bean id="jsonConverter" class="org.example.JsonConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
如果你使用 Java 配置,它几乎一样简单:
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired JsonConverter jsonConv;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jsonConv);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
converters.add(new ByteArrayHttpMessageConverter());
converters.add(stringConverter);
converters.add(new ResourceHttpMessageConverter());
converters.add(new SourceHttpMessageConverter<Source>());
converters.add(new AllEncompassingFormHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
}
}
但在这里您必须明确添加您需要的所有默认消息转换器。