本文来自网易云社区
作者:司徒诚颢
Jackson是一个功能强大的Java序列化库。除了支持常用的json,同时还支持Smile,BSON,XML,CSV,YAML。
接下来就介绍一些处理json时常见的使用场景,文中的例子都是在1.9版本下运行的。
Jackson的json库提供了3种API:
其中最常用到的就是Data Binding了,基本的用法如下
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(foo);
Foo foo = mapper.readValue(json, Foo.class);
ObjectMapper是线程安全的,应该尽量的重用。
需要注意的是,Jackson是基于JavaBean来序列化属性的,如果属性没有GETTER方法,默认是不会输出该属性的。
但是在序列化的时候,经常会有特殊的需求来对输出的结果进行自定义。
比如不输出某几个属性,或者自定义属性的名字,等等。
Jackson提供了非常多的方法来满足我们的自定义需求。
假设有这么一个对象:
class User {
private long id;
private String name;
private String avator240;
private String avator160;
private String address;
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getAvator240() {
return avator240;
}
public String getAvator160() {
return avator160;
}
public String getAddress() {
return address;
}
}
如果不想输出id
,最简单的方法,就是给该属性加上注解JsonIgnore
:
@JsonIgnore
private long id;
或者
@JsonIgnore
public long getId() {
return id;
}
因为
JsonIgnore
的target可以是CONSTRUCTOR, FIELD, METHOD 如果不想输出多个属性,比如
id
,
address
和
avator160
,除了在每个属性上添加
JsonIgnore
,也可以直接在类上添加注解
JsonIgnoreProperties
:
@JsonIgnoreProperties({"id","avator160","address"})
class User {
这里的User
类只有5个属性,使用annotation控制忽略哪些属性还是绰绰有余的。
加入有一个类有上百个属性,如果只想输出其中的10来个属性,使用JsonIgnore
就显得太繁琐了。
此时就可以使用JSON View或MixIn Annotation了。
先来看一下JSON View,和数据库的view一样,可以为一个对象创建view,输出时只会输出view中定义的那些属性。 特别的,一个对象可以定义任意多个view,同时view也是可以继承的。
先来看看如何使用view来过滤id
,address
和avator160
:
public class JsonViewDemo {
private static class User {
private long id;
@JsonView({FilterView.Output.class})
private String name;
@JsonView({FilterView.Output.class})
private String avator240;
private String avator160;
private String address;
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getAvator240() {
return avator240;
}
public String getAvator160() {
return avator160;
}
public String getAddress() {
return address;
}
}
private static class FilterView {
static class Output {}
}
public static void main(String[] args) throws Exception {
User user = new User();
user.id = 1000L;
user.name = "test name";
user.avator240 = "240.jpg";
user.avator160 = "160.jpg";
user.address = "some address";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
System.out.println(mapper.writerWithView(FilterView.Output.class).writeValueAsString(user));
}
}
首先需要定义一个需要输出的属性的ViewFilterView.Output
,然后在需要输出属性上声明该View,之后使用writerWithView(FilterView.Output.class)
来序列化就可以了。
需要注意的是,在这里需要把DEFAULT_VIEW_INCLUSION
设置为true
,因为默认是会输出没有JsonView
注解的属性的。
其实View的作用远不止如此,再来看一个更实用的例子: 假设现有个API接口,需要针对不同的客户端(ios,android)输出不同的属性,通过创建多个View就能轻松完成。
public class JsonApiViewDemo {
private static class User {
private long id;
@JsonView({ApiView.Default.class})
private String name;
@JsonView({ApiView.Ios.class})
private String avator240;
@JsonView({ApiView.Android.class})
private String avator160;
private String address;
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public String getAvator240() {
return avator240;
}
public String getAvator160() {
return avator160;
}
}
private static class ApiView {
static class Default {}
static class Ios extends Default {}
static class Android extends Default {}
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
User user = new User();
user.id = 10000L;
user.name = "test name";
user.avator240 = "240.jpg";
user.avator160 = "160.jpg";
user.address = "some address";
String apiViewJson = mapper.writerWithView(ApiView.Default.class).writeValueAsString(user);
String iosViewJson = mapper.writerWithView(ApiView.Ios.class).writeValueAsString(user);
String androidViewJson = mapper.writerWithView(ApiView.Android.class).writeValueAsString(user);
System.out.println(apiViewJson);
System.out.println(iosViewJson);
System.out.println(androidViewJson);
}
}
使用ApiView.Ios
只会输出name
和avator240
。 使用ApiView.Android
只会输出name
和avator160
。 但是,以上的所有方法都有一个缺点,那就是需要修改源代码,它们都需要在要输出的类上加上annotation。 假设没有那些要序列化的类的源代码,甚至那些类都不符合JavaBean规范,又该怎么办呢? 此时就可以使用MixIn Annotation了,其实和View差不多,也相当于是为要序列化的对象定义了一个View。
public class JsonMixInDemo {
static class User {
private long id;
private String name;
private String avator240;
private String avator160;
private String address;
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
abstract class MixIn {
@JsonIgnore abstract int getAddress();
@JsonIgnore long id;
@JsonProperty("custom_name") abstract String getName();
@JsonProperty("avator") String avator240;
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
User user = new User();
user.id = 1234567L;
user.name = "test name";
user.avator240 = "240.jpg";
user.avator160 = "160.jpg";
user.address = "some address";
mapper.getSerializationConfig().addMixInAnnotations(User.class, MixIn.class);
String json = mapper.writeValueAsString(user);
System.out.println(json);
}
}
将输出
{"custom_name":"test name","avator":"240.jpg"}
其中关键在于MixIn这个类,MixIn也可以定义成接口。 在这里,既可以过滤属性/方法,也可以定义哪些属性/方法会被输出,顺便还可以自定义输出的属性名。 在序列化前只要配置一下
addMixInAnnotations(User.class, MixIn.class)
就可以在完全不修改该类的情况下自定义输出了。 MixIn Annotation应该能满足几乎所有需要对属性进行自定义的情况了,但是MixIn Annotation的配置是静态的,不能在运行时修改。 结合JSON Filter和Mixin就可以实现动态的过滤属性了
public class JsonFilterDemo {
private static class User {
private long id;
private String name;
private String avator240;
private String avator160;
private String address;
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public String getAvator240() {
return avator240;
}
public String getAvator160() {
return avator160;
}
public long getId() {
return id;
}
}
@JsonFilter("userFilter")
private static interface UserFilterMixIn
{
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
User user = new User();
user.id = 1000L;
user.name = "test name";
user.avator240 = "240.jpg";
user.avator160 = "160.jpg";
user.address = "some address";
FilterProvider idFilterProvider = new SimpleFilterProvider().addFilter("userFilter", SimpleBeanPropertyFilter.filterOutAllExcept(new String[]{"name", "avator240"}));
mapper.setFilters(idFilterProvider);
mapper.getSerializationConfig().addMixInAnnotations(User.class, UserFilterMixIn.class);
String userFilterJson = mapper.writeValueAsString(user);
System.out.println(userFilterJson);
}
}
前面介绍了很多自定义输出属性的方法,如果需要在序列化时修改值,要怎么办呢?
只要实现自己的JsonSerializer
就可以了,下面这个例子就会输出id的md5值
public class JsonCustomSerializerDemo {
static class User {
@JsonSerialize(using = Md5IdSerializer.class)
private long id;
private String name;
private String address;
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
}
public static class Md5IdSerializer extends JsonSerializer {
public void serialize(Long value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeString(md5(value));
}
private String md5(Long value) {
return value + "-md5-mock";
}
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
User user = new User();
user.id = 1234567L;
user.name = "test name";
user.address = "some address";
String json = mapper.writeValueAsString(user);
System.out.println(json);
}
}
接下来看一下反序列化,现在很多网站都开放了api接口,支持json格式的返回。
比如在调用了某个api后,需要解析返回的json数据获取信息,这种情况下为json创建一个对应的类是很不方便的。
此时使用Tree Model来解析json就比较方便了,下面这段代码就是用来解析人人网的用户信息的
JsonNode root = mapper.readTree(rerenjson);
JsonNode user = root.get("user");
String id = user.get("id").asText();
String name = user.get("name").asText();
JsonNode avators = user.get("avatar");
if (avators.isArray()) {
for (Iterator it = avators.getElements(); it.hasNext(); ){
JsonNode avator = it.next();
if ("tiny".equals(avator.get("type").asText())) {
String ava = avator.get("url").asText();
break;
}
}
}
最后列一些使用Jackson的最佳实践:
上面这些tips都摘自https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance,上面还有更多tips。
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。