json格式是进行数据传输的一种常见的格式,尤其是在web开发中,http请求的body体一般都是json格式的,早些年xml格式是主流的数据传输格式,但逐渐被形式更加简洁明了的json格式取代。Java开源的json类库有很多种,比较常见的有:
- fastjson
- jackson
- Gson
- Json-lib
工作中遇到最多的是fastjson和jackson,因此本文重点介绍这两个json库。
1、fastjson
1.1 简介
FastJson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean,相比其他json类库,fastjson有以下特点:
- 速度快,在简单对象和复杂对象的序列化和反序列化的速度上比第二名的jackson快了20%;
- api使用简单,提供的api多是静态方法,相比jackson使用是需要新建实例对象,fastjson使用起来更加方便;
- 有漏洞,在AutoType上存在漏洞,具体看参考链接2,所以有些公司禁止使用fastjson了,但感觉用的还是很多,之前华为倒是漏洞扫描时扫出来了很多还专门整改了。
maven依赖:
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.73</version></dependency>
1.2 数据结构
1.2.1 JSONObject && JSONArray
fastjson里针对json对象和数组设计了对应的数据结构:
- JSONObject:一个json对象,即一个key:value组成的键值对,{},JSONObject类实现了Map接口,因此可以直接向JSONObject用put方法添加k-v;
- JSONArray:一个json数组,由若干个JSONObject组成的数组,[{}, {}, {}],JSONArray类实现了List接口,因此可以直接向JSONArray用add方法添加JSON对象,其中getJSONObject(int index)方法用来获取指定下标的JSONObject对象。
举个例子:
package com.Jerry.json.fastjson;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
public class ObjectArrayDemo {
public static void main(String[] args) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "jerry");
jsonObject.put("age", 27);
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("name", "Cissie");
jsonObject1.put("age", 28);
JSONArray jsonArray = new JSONArray();
jsonArray.add(jsonObject);
jsonArray.add(jsonObject1);
System.out.println(jsonObject);
System.out.println(jsonObject1);
System.out.println(jsonArray);
}
}
结果:
{"name":"jerry","age":27}
{"name":"Cissie","age":28}
[{"name":"jerry","age":27},{"name":"Cissie","age":28}]
1.2.2 api使用
判断JSONObject里是否含有指定的key
jsonObject.containsKey("key")这个方法是Map接口的,JSONObject实现了Map接口,在HashMap里也有containsKey方法。
从复杂的JSONObject里获取子JSONObject
String str = "{\"courses\":[{\"courseName\":\"Math\",\"score\":99},{\"courseName\":\"Computer\"," + "\"score\":100},{\"courseName\":\"English\",\"score\":98}],\"dog\":{\"color\":\"white\",\"dogAge\":1,\"dogName\":\"miumiu\",\"variety\":\"British Shorthair Cat\"},\"id\":\"ff6e8dec-630f-47ad-b1f2-99e739cea49d\",\"studentAge\":27,\"studentName\":\"Jerry\"}"; JSONObject jsonObject = JSON.parseObject(str); JSONObject jsonObject1 = jsonObject.getJSONObject("dog");从复杂的JSONObject里获取子JSON字符串
String str = "{\"courses\":[{\"courseName\":\"Math\",\"score\":99},{\"courseName\":\"Computer\"," + "\"score\":100},{\"courseName\":\"English\",\"score\":98}],\"dog\":{\"color\":\"white\",\"dogAge\":1,\"dogName\":\"miumiu\",\"variety\":\"British Shorthair Cat\"},\"id\":\"ff6e8dec-630f-47ad-b1f2-99e739cea49d\",\"studentAge\":27,\"studentName\":\"Jerry\"}"; JSONObject jsonObject = JSON.parseObject(str); String str1 = jsonObject.getString("dog");JSONObject转字符串
String str = jsonObject.toJSONString();toJSONString底层实现还是Object类的toString方法。
字符串转JSONObject
JSONObject jsonObject = JSON.parseObject(str);JSONObject转JavaBean
暂无直接的api,可以用JSONObject -> JSONString -> JavaBean。
- JavaBean转JSONObject
暂无直接的api,可以用JavaBean-> JSONString -> JSONObject 。
将JSON的字符串数组转换成JSONArray
JSONArray jsonArray = JSON.parseArray(JSON_ARRAY_STR);将JSONArray转换成JSON的字符串数组
String s = JSON.toJSONString(jsonArray);上面的api工作中用的不多,最多的还是下面的序列化反序列化和
@JSONField注解。entrySet()
由于JSONObject实现了Map接口,因此Map的一些方法,比如entrySet()、containsKey()在JSONObject里也有实现。
1.3 序列化&&反序列化
- 序列化:将JavaBean转换成JSON字符串,使用JSON.toJSONString(JavaBean);
- 反序列化:将JSON字符串转换成JavaBean,使用JSON.parseObject(str, JavaBean.class())。
举例:
Dog的JavaBean:
@Data
public class Dog {
private String dogName;
private int dogAge;
private String color;
private String variety;
}
Course的JavaBean:
@Data
@AllArgsConstructor
public class Course {
private String courseName;
private int score;
}
需要序列化的Student的JavaBean:
@Data
public class Student {
private String studentName;
private int studentAge;
private String id;
private List<Course> courses;
private Dog dog;
}
把Student实例的初始化动作封装在一个工具类里:
package com.Jerry.json;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Utils {
public static Student generateStudent()
{
Student student = new Student();
student.setStudentName("Jerry");
student.setStudentAge(27);
student.setId(UUID.randomUUID().toString());
List<Course> courses = new ArrayList<>();
courses.add(new Course("Math", 99));
courses.add(new Course("Computer", 100));
courses.add(new Course("English", 98));
Dog dog = new Dog();
dog.setDogName("miumiu");
dog.setDogAge(1);
dog.setColor("white");
dog.setVariety("British Shorthair Cat");
student.setCourses(courses);
student.setDog(dog);
return student;
}
}
Student对象序列化:
public class FastjsonDemo {
public static void main(String[] args) {
Student student = Utils.generateStudent();
// 序列化
String str = JSON.toJSONString(student);
System.out.println(str);
System.out.println(str.getClass());
// 反序列化
Student student1 = JSON.parseObject(str, Student.class);
System.out.println(student1);
System.out.println(student1.getClass());
}
}
结果:
{"courses":[{"courseName":"Math","score":99},{"courseName":"Computer","score":100},{"courseName":"English","score":98}],"dog":{"color":"white","dogAge":1,"dogName":"miumiu","variety":"British Shorthair Cat"},"id":"f707af53-aca8-4ba6-9ef3-852a6d309ece","studentAge":27,"studentName":"Jerry"}
class java.lang.String
Student(studentName=Jerry, studentAge=27, id=f707af53-aca8-4ba6-9ef3-852a6d309ece, courses=[Course(courseName=Math, score=99), Course(courseName=Computer, score=100), Course(courseName=English, score=98)], dog=Dog(dogName=miumiu, dogAge=1, color=white, variety=British Shorthair Cat))
class com.Jerry.json.Student
fastjson好像还有定制序列化和反序列化的api,用到的时候再学。
1.4 注解@``JSONField
fastjson里最重要一个注解就是@JSONField,该注解一般标注在POJO里的属性字段上,下面具体介绍一下该注解的使用。
1.4.1 @JSONField的使用注意
@JSONField的作用对象:
- Field
- Setter 和 Getter 方法
但一般还是作用在属性字段Field上,配合name等参数使用。
注意:
- FastJson 在进行操作时,是根据 getter 和 setter 的方法进行的,并不是依据 Field 进行;
若POJO的属性是私有的,必须有set方法,否则无法反序列化,直接一个
@Data注解完事。1.4.2
@JSONField的参数name
name参数主要是为了解决json对象的key和JavaBean里的属性字段名不一致的情况,比如json对象里的key是user_id,而POJO里对应的属性字段名称为userId(Java驼峰命名导致这种情况经常出现),为了序列化和反序列化POJO时字段对应的值能被正常解析(否则对应字段的值为null或抛找不到该字段的异常),可以在POJO上这样做:
@Data
public class User {
@JSONField(name="user_id")
private String userId;
}
name里是json字符串里的key的字段名称。
- ordinary
默认 fastjson 序列化一个 java bean,是根据属性字段Field的字母序进行序列化的,可以通过ordinal参数指定序列化时字段的顺序,ordinary参数的值从1开始计数,这个特性需要 1.1.42 以上版本。
上面Student对象序列化时,显示的json字符串里,key的顺序为:course、dog、id、studentAge、studentName,默认是按照key的字母序升序排序,如果想按照如下顺序显示:id、studentAge、dog、studentName、course,可以在POJO里这样做:
@Data
public class Student {
@JSONField(ordinal = 4)
private String studentName;
@JSONField(ordinal = 2)
private int studentAge;
@JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 5)
private List<Course> courses;
@JSONField(ordinal = 3)
private Dog dog;
}
结果:
{"id":"ea5498c1-95d7-406d-95af-6bd12969c7ac","studentAge":27,"dog":{"color":"white","dogAge":1,"dogName":"miumiu","variety":"British Shorthair Cat"},"studentName":"Jerry","courses":[{"courseName":"Math","score":99},{"courseName":"Computer","score":100},{"courseName":"English","score":98}]}
- serialize/deserialize
使用 serialize/deserialize 指定字段不序列化,默认是序列化和反序列化,即serialize=true和deserialize=true,serialize=false表示字段不被序列化,deserialize=false表示字段不被反序列化
@JSONField(serialize=false, deserialize=false)
private Date date;
- format
使用format配置日期格式化,这个我暂时还没用到,不过body体里有个createTime和deleteTime等字段信息还是会用到的,举个例子:
// 配置date序列化和反序列使用yyyyMMdd日期格式
@JSONField(format="yyyyMMdd")
private Date date;
2、jackson
2.1 简介
Jackson是一个简单的、功能强大的、基于Java的应用库。它可以很方便完成Java对象和Json对象(xml文档or其它格式)进行互转。Jackson社区相对比较活跃,更新速度也比较快。
Jackson库有如下几大特性:
- 高性能且稳定:低内存占用,对大/小JSON串,大/小对象的解析表现均很优秀 ;
- 流行度高:是很多流行框架的默认选择 ;
- 容易使用:提供高层次的API,极大简化了日常使用案例 ;
- 无需自己手动创建映射:内置了绝大部分序列化时和Java类型的映射关系;
- 干净的JSON:创建的JSON具有干净、紧凑、体积小等特点 - 无三方依赖:仅依赖于JDK - Spring生态加持:jackson是Spring家族的默认JSON/XML解析器。
上面是我从参考文章中摘录的对jackson的描述,说实话以上几点我在平时工作中体会几乎为0,使用fastjson就是图他的api简单,直接调用静态方法即可;促使我学习和使用jackson的动力是fastjson有漏洞,公司不鼓励用了,这才学习jackson,而且jackson的api使用确实没fastjson简单(api是实例方法,必须先new一个ObjectMapper),但是fastjson的速度快我工作中体会和收益也不大,毕竟这些json库的性能满足日常用还是够的。老外喜欢用jackson,国人喜欢用fastjson。
maven依赖:
<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-core-asl -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
2.2 数据结构
2.2.1 JsonNode && ObjectNode && ObjectMapper
- JsonNode
fastjson里将json对象对应一个数据结构:JSONObject,jackson里也设立了一个数据结构对应json对象:JsonNode。jackson里的jsonNode模型是一个树的模型,你可以看到有些api名称,比如createObjectNode都体现了这一点,即把json这个数据结构与树模型对应上,每个json对象对应一个jsonNode(树节点)。JsonNode类是个抽象类,ObjectMapper的方法一般返回的类型都是JsonNode。
- ObjectNode
上面提到了JsonNode是个抽象类,而ObjectNode是个实体类,JsonNode是不可变的,不能直接对JsonNode进行增删改的操作,可以通过objectMapper.createObjectNode()方法生成一个ObjectNode,在这个ObjectNode上通过objectNode.put(key, value)和objectNode.set(“child1”, childNode)来进行增删改的操作。
- ObjectMapper
Jackson 在使用之前需要实例化一个 ObjectMapper 对象(它不直接提供全局的默认静态方法),通常我们会将 objectMapper 定义成一个静态成员,或通过 DI 框架注入使用,通过对ObjectMapper对象进行一系列的configure和set方法,所有序列化和反序列化将按照set方法制定的规则进行,具体的一个ObjectMapper的配置如下:
private final static ObjectMapper MAPPER;
static {
MAPPER = new ObjectMapper()
// 自动加载 classpath 中所有 Jackson Module
.findAndRegisterModules()
// POJO类字段为空或者null是不参加序列化
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 时区序列化为 +08:00 形式
.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, false)
// 日期、时间序列化为字符串
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
// 持续时间序列化为字符串
.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
// 当出现 Java 类中未知的属性时不报错,而是忽略此 JSON 字段
.configure(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS, false)
// 枚举类型调用 `toString` 方法进行序列化
.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true)
// 设置 java.util.Date 类型序列化格式
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
// 设置 Jackson 使用的时区
.setTimeZone(SimpleTimeZone.getTimeZone("GMT+8"));
}
2.2.2 api使用
判断JsonNode里是否含有指定的key
jsonNode.has("id")objectNode里添加k-v值
ObjectNode objectNode = new ObjectNode(); objectNode.put(key, value);注意JsonNode是不可变的,不能直接对JsonNode进行增删改的操作,需要用ObjectNode。
json字符串转换成jsonNode
JsonNode rootNode = objectMapper.readTree(jsonStr);jsonNode转换成json字符串
String jsonStr = objectMapper.writeValueAsString(rootNode);jsonNode里获得子jsonNode
JsonNode jsonNode1 = jsonNode.get("courses");其中jsonNode是父jsonNode,jsonNode1是子jsonNode,get方法里传的参数是父jsonNode里子jsonNode的key字段名称。
jsonNode获取对应的value值 ```java // json的value是字符串 String jsonValue = jsonNode.get(key字段名称).asText();
// json的value是int值 int jsonValue = jsonNode.get(key字段名称).asInt();
// json的value是boolean值 boolean jsonValue = jsonNode.get(key字段名称).asBoolean();
- **拷贝一个jsonNode**
```java
ObjectNode newJsonNode = objectMapper.createObjectNode();
newJsonNode.setAll((ObjectNode) oldJsonNode);
注意JsonNode是不可变的,不能直接对JsonNode进行增删改的操作,需要用ObjectNode。
2.3 序列化
jackson序列化有两个api:
- writeValue(参数,obj):直接将传入的对象序列化为json,并且返回给客户端,其中参数有四种形式:
- file 将转换后的json字符串保存到指定的file文件中
- writer 将转换后的json字符串保存到字符输出流中
- outputStream将转换后的json字符串保存到字节输出流中
- jsonGenerator类(不常用,不作介绍)
- writeValueAsString(obj):将传入的对象序列化为json,返回给调用者,这也是jackson里最经常用的序列化方法。
上面两个方法都需要进行异常处理,要么用try-catch子句包围,要么直接往上抛异常。
举例:
public static void main(String[] args) {
Student student = Utils.generateStudent();
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = "";
// 序列化
try {
jsonStr = objectMapper.writeValueAsString(student);
System.out.println(jsonStr);
System.out.println(jsonStr.getClass());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
结果:
{"studentName":"Jerry","studentAge":27,"id":"25c41f29-56e2-44d2-94c1-ae829096777b","courses":[{"courseName":"Math","score":99},{"courseName":"Computer","score":100},{"courseName":"English","score":98}],"dog":{"dogName":"miumiu","dogAge":1,"color":"white","variety":"British Shorthair Cat"}}
class java.lang.String
2.4 反序列化
jackson反序列化仅有一个接口:readValue,但readValue方法有很多重载形式,如下:
// 当POJO结构比较简单,直接传POJO.class
public <T> T readValue(String content, Class<T> valueType);
// 当POJO结构比较复杂,用TypeReference<T>
public <T> T readValue(String content, TypeReference<T> valueTypeRef);
// 这个书写起来比较麻烦,就不说明了,不常用,前2个已经彻底满足了
public <T> T readValue(String content, JavaType valueType);
举例:
简单类型的POJO:
@Data
public class User {
private String userId;
private String name;
private int age;
}
复杂类型的POJO见上面的Student实体类。
package com.Jerry.json.jackson;
import com.Jerry.json.Student;
import com.Jerry.json.utils.Utils;
import com.Jerry.json.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class JacksonDemo {
public static void main(String[] args) {
Student student = Utils.generateStudent();
ObjectMapper objectMapper = new ObjectMapper();
// 简单POJO反序列化
try {
String jsonStr = "{\"userId\":\"c3bf9543-4137-45a1-8335-135ae8e2a7f6\",\"name\":\"Jerry\",\"age\":27}";
User user = objectMapper.readValue(jsonStr, User.class);
System.out.println(user);
System.out.println(user.getClass());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// 复杂POJO反序列化
try {
String jsonStr = "{\"courses\":[{\"courseName\":\"Math\",\"score\":99},{\"courseName\":\"Computer\",\"score\":100},{\"courseName\":\"English\",\"score\":98}],\"dog\":{\"color\":\"white\",\"dogAge\":1,\"dogName\":\"miumiu\",\"variety\":\"British Shorthair Cat\"},\"id\":\"f707af53-aca8-4ba6-9ef3-852a6d309ece\",\"studentAge\":27,\"studentName\":\"Jerry\"}";
Map<String, Object> studentMap = objectMapper.readValue(jsonStr, new TypeReference<Map<String,Object>>() {
});
System.out.println(studentMap);
System.out.println(studentMap.getClass());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
结果:
User(userId=c3bf9543-4137-45a1-8335-135ae8e2a7f6, name=Jerry, age=27)
class com.Jerry.json.User
{courses=[{courseName=Math, score=99}, {courseName=Computer, score=100}, {courseName=English, score=98}], dog={color=white, dogAge=1, dogName=miumiu, variety=British Shorthair Cat}, id=f707af53-aca8-4ba6-9ef3-852a6d309ece, studentAge=27, studentName=Jerry}
class java.util.LinkedHashMap
注意:
当POJO是复杂类型时,反序列化时不是直接得到对应的Bean,而是一个Map类型的beanMap,此时readValue方法里的第二个参数是TypeReference,里面一般会是个泛型T。
2.5 注解
jackson里的注解有很多,这里介绍一下常用的注解。
**@JsonProperty**
该注解标注在属性字段上,用来处理json对象里的key和POJO里的属性名称不一致的情况,与fastjson里的@JSONField一个用处。该注解有几个参数:
- value:属性字段和json里的key的映射关系,代表属性字段对应的json里的key,对应fastjson里的name;
- index:对应fastjson里的ordinary?(不是)
- defaultValue:默认值?
**@JsonIgnore**
该注解用来标注在属性字段上,用来表示该属性不参与序列化,举例:
@JsonIgnore
@JsonProperty("userName")
private String name;
**@JsonIgnoreProperties**
该注解用来标注在POJO类上,用来表示该POJO类里哪些字段不参与序列化,举例:
@Data
@JsonIgnoreProperties(value={"name","userAge"})
public class Person {
@JsonIgnore
@JsonProperty("userName")
private String name;
@JsonProperty("userAge")
private Integer age;
@JsonProperty("userHeight")
private Integer height;
}
@JsonProperty注解好像在反序列化时没有发挥作用,在实际使用时发现的!应该是我使用姿势不对~~~
2.6 jackson工具类
上面的介绍可以看出,jackson的api使用就是没fastjson方便,fastjson直接用静态方法就可以了,而jackson还需要new一个ObjectMapper,反序列化复杂类型的对象时还不能直接得到JavaBean,而是一个Map类型的beanMap,而且ObjectMapper对象可能需要一些复杂的设置(通过configure方法完成),因此项目里使用jackson作为json库时,一般会封装一个Utils,Utils里提供静态方法供外部使用,这里参考链接里的utils,如下:
JacksonUtils:
package com.Jerry.json.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.text.SimpleDateFormat;
import java.util.SimpleTimeZone;
public class JacksonUtils {
private final static ObjectMapper MAPPER;
static {
MAPPER = new ObjectMapper()
// 自动加载 classpath 中所有 Jackson Module
.findAndRegisterModules()
// 时区序列化为 +08:00 形式
.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, false)
// 日期、时间序列化为字符串
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
// 持续时间序列化为字符串
.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
// 当出现 Java 类中未知的属性时不报错,而是忽略此 JSON 字段
.configure(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS, false)
// 枚举类型调用 `toString` 方法进行序列化
.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true)
// 设置 java.util.Date 类型序列化格式
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
// 设置 Jackson 使用的时区
.setTimeZone(SimpleTimeZone.getTimeZone("GMT+8"))
// POJO类字段为空或者null是不参加序列化
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
public static String serialize(Object obj) {
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static <T> T deserialize(String jsonText, TypeReference<T> type) {
try {
return MAPPER.readValue(jsonText, type);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static <T> T deserialize(String jsonText, Class<T> beanClass) {
try {
return MAPPER.readValue(jsonText, beanClass);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static JsonNode deserialize(String jsonText) {
try {
return MAPPER.readTree(jsonText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3、总结
参考:
Fastjson 等等,四种 Java 常用 JSON 库性能比较
fastjson到底做错了什么?为什么会被频繁爆出漏洞?
fastjson的JSONArray和JSONObject
Fastjson 简明教程
初识Jackson — 世界上最好的JSON库
Fastjson到了说再见的时候了
Jackson序列化和反序列化
Jackson objectMapper.readValue 方法 详解
Jackson JsonNode
JSON 之 Jackson
