现在我们很多的Java后端项目都基于spring boot。在写REST接口的时候,返回值大多是json格式的,参数有些情况下也是json格式的(@RequestBody)。
枚举相关
我们在项目中经常会遇到返回的pojo
或者vo
等对象或者参数里包含枚举类。我们今天来聊几个和枚举类相关的需求,看看解决方案。
需求1:返回值是枚举时,如何显示枚举中的变量值而不是显示默认的字面量
接口
@RestController
public class DemoController {
@GetMapping("/demo")
public GenderEnum getGenderEnum() {
return GenderEnum.MALE;
}
}
枚举类
@Getter
public enum GenderEnum {
MALE("男"),
FEMALE("女")
;
private String desc;
GenderEnum(String desc) {
this.desc = desc;
}
}
默认的返回值长这样
返回值你看现在是返回"MALE",我们现在是需要返回"男",只需要在枚举类GenderEnum
这样写
@JsonValue
public String getGender() {
return this.desc;
}
加一个方法,然后返回你想返回的字段。上面在加上一个@JsonValue
注解。现在返回值就变成这样了
需求2:给后端传的参数中有枚举,需要可以指定传字面量还是变量值
经过上面的改造之后,我们可以正常的返回变量的值了。但是我们现在来试一下调用接口时参数为枚举时情况如何
接口
@PostMapping("/demo")
public void postGenderEnum(@RequestParam GenderEnum gender) {
System.out.println(gender);
System.out.println("调用成功");
}
我们传“MALE”试试,我们用restlet client
插件调用接口
报错了,报错信息如下
{
"timestamp": "2019-06-21T13:18:42.041+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize value of type `com.atom.celebrityvote.enums.GenderEnum` from String \"MALE\": value not one of declared Enum instance names: [女, 男]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.atom.celebrityvote.enums.GenderEnum` from String \"MALE\": value not one of declared Enum instance names: [女, 男]\n at [Source: (PushbackInputStream); line: 1, column: 1]",
"path": "/demo"
}
报错的大意就是需要传"男"、"女",不能传"MALE"、"FEMALE"。这是因为刚才我们设置了@JsonValue
的原因。如果我们需要传"MALE"、"FEMALE"的话需要在GenderEnum里面这么写
@JsonCreator
public static GenderEnum createGender(String gender) {
GenderEnum[] values = GenderEnum.values();
for (GenderEnum value : values) {
if (value.name().equals(gender)) {
return value;
}
}
return null;
}
新增一个方法,上面写上@JsonCreator
注解,方法里面写上反序列化的逻辑,如果无法反序列化的话就直接返回null就完了。再试一次
好了,成功。
jsonview相关
相信大家都使用过@JsonView
注解来过滤返回对象的字段,使用的还挺欢脱。但是如果大家使用jpa搞数据库,且出现了分页查询的业务需求,那么可能就需要返回Page对象了,此时@JsonView
失效了,明明查到了数据但是返回空。
上面说这个问题是因为啥呢?是因为Page接口里的所有字段或方法都没有指定视图,所以spring boot在序列化的时候就懵圈了,不知道该返回啥,就返回了空。
那解决方案有没有呢?有,而且不止一种。我这里说几种,但是只提供一种实现。
解决思路
第一种
自定义一个Page接口的序列类,实现序列化方法。并且将Page接口的序列化交给这个类,这样spring boot在序列化Page接口的时候就会调用你实现的方法,搞定!这种方法呢在https://stackoverflow.com/questions/30221151/spring-mvc-use-jsonview-on-spring-data-page里用户user3864028
回答了,我就不写了。(这一段这样写有bug,依赖这个用户,如果这个用户改了用户名那么就会影响一部分读者阅读。暂时就希望这个用户这段时间不改用户名吧)
第二种
自己写一个VO,封装Page接口。再写一个序列类,实现该VO的序列方法,将该VO的序列化交给这个类来做。读到这呢,可能大家就会说,这种方法和上面那种是一毛一样的呀,还多了一个VO类,更麻烦。但实际上这种方法的配置更少,也更直观。如果大家看不懂上面那种解决方案的配置的话,就按照我的这种思路来解决,也挺优雅的。我会教大家写这种。接口返回自己写的这个VO而不是原来的Page哦
第三种
自己写一个VO,封装Page接口,然后在该VO里获取字段的get方法上指定视图。这种方法最简单,也最笨,不易扩展,相信大家都懂我啥意思了,就不啰嗦了。
解决方案(第二种思路)
首先搭一个环境
实体类
public class User {
@JsonView(View.User.simple.class)
private String username;
@JsonView(View.User.detail.class)
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
视图类
public interface View {
interface User {
interface simple {}
interface detail extends simple {}
}
}
接口
@RestController
public class DemoController {
@JsonView(View.User.detail.class)
@GetMapping("/demo")
public Page<User> getUsers() {
//不接入数据库,构造一个Page返回
List<User> userList = new ArrayList<>();
userList.add(new User("user1", "pass1"));
userList.add(new User("user2", "pass2"));
userList.add(new User("user3", "pass3"));
return new PageImpl<>(userList);
}
}
环境搭建完成了,启动项目调用接口
调接口
不出所料,返回为空,但其实我们是有点东西的。
好的,现在我们开始来写一个VO封装Page。只取Page中的部分字段。
public class PageVO<T> {
private Page<T> page;
public PageVO(Page<T> page) {
this.page = page;
}
public long getTotalElements() {
return page.getTotalElements();
}
public List<T> getContent() {
return page.getContent();
}
public boolean isFirst() {
return page.isFirst();
}
public boolean isLast() {
return page.isLast();
}
}
然后再写一个序列类
public class PageVOSerializer extends JsonSerializer<PageVO> {
@Override
public void serialize(PageVO value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
ObjectMapper om = new ObjectMapper().disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
gen.writeStartObject();
gen.writeNumberField("totalElements", value.getTotalElements());
gen.writeBooleanField("last", value.isLast());
gen.writeBooleanField("first", value.isFirst());
gen.writeFieldName("content");
gen.writeRawValue(
om.writerWithView(serializers.getActiveView()).writeValueAsString(value.getContent())
);
gen.writeEndObject();
}
}
来解释一下上面的代码。先看方法体的第一行
ObjectMapper om = new ObjectMapper().disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
DEFAULT_VIEW_INCLUSION的源码注释里写的是
这就很清晰了,我们diable之后没有JsonView注解的字段就不会被序列化了。
然后再看方法体倒数第三行
om.writerWithView(serializers.getActiveView()).writeValueAsString(value.getContent())
这一行的意思是说在序列化这个对象的时候使用我们为其指定的视图,Page接口的content字段背后就是我们的实体类哦。
好了,到这我们改写的东西全部写完了,只需要给PageVO指定序列类PageVOSerializer,然后接口的返回改为PageVO就齐活了。
指定序列类,只需要在PageVO
类上加一个注解就完了
@JsonSerialize(using = PageVOSerializer.class)
public class PageVO<T> {
//...
}
修改controller
@RestController
public class DemoController {
@JsonView(View.User.detail.class)
@GetMapping("/demo")
public PageVO<User> getUsers() {
//不接入数据库,构造一个Page返回
List<User> userList = new ArrayList<>();
userList.add(new User("user1", "pass1"));
userList.add(new User("user2", "pass2"));
userList.add(new User("user3", "pass3"));
return new PageVO<>(new PageImpl<>(userList));
}
}
好了,现在可以测试一下了。
测试结果
好了,大家可以继续欢脱了!
返回null和空集
在项目开发中我们有时候会需要过滤字段值为null的字段或者为空集的字段,如果一个字段的值是null或者是空list或者set等空集,我们就不返回该字段。这怎么来操作呢?
- 第一种我们可以在返回的对象类上加上
@JsonInclude(JsonInclude.Include.NON_EMPTY)
注解 - 全局配置,所有的返回都适用
spring:
jackson:
default-property-inclusion: non_empty
上面两种方法中都有non_empty
,这个是指不返回null和空集,如果只需要过滤null的话改为non_null
即可
网友评论