简介
JSON:JavaScript Object Notation,JS 对象简谱,是JS对象的字符串表示法,它使用文本表示一个JS对象的信息,本质是一个字符串,后来被用作数据交换,所以也是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储和表示数据,简洁和清晰的层次结构使得JSON成为理想的数据交换语言。
JSON是一种特殊格式的字符串,包含六个构造字符、字符串、数字和三个字面名。
- 六个构造字符:[ ] { } : ,
- 字符串:是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义双引号
- 三个字面量:false、true、null。
- 数组:数组中的元素存放在中括号中,用逗号分割
- 对象:对象中的内容是键值对,存放在大括号中,键值对之间由逗号分割,键和值之间由冒号分割
json字符串的案例:
{
"key1": "value1",
"key2": "value2",
"key3": [
"v1",
"v2",
"v3",
false
],
"key4": true,
"key5": {
"key6": "value6"
}
}
java中常用的json处理框架,包括jackson、gson、fastjson,在日常开发中,这三个都使用过,但是从来没有深入地了解过,在这里,对它们做一个深入地了解
json处理框架
jackson
java中用来处理json数据的框架,是spring mvc默认的json解析器,由fastxml公司开源。
基本使用
第一步:添加依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
第二步:编写jackson的工具类。我把jackson的基本使用都总结到了这个工具类中
public class JacksonUtil {
// 1. jackson的基本配置:首先,它是线程安全的,多线程共用一个ObjectMapper,效率会更高,
// 但是要注意,运行过程中不可以修改ObjectMapper的配置信息,如果有特殊需求,要定制配置,
// 建议创建一个新的实例
private static final ObjectMapper MAPPER = new ObjectMapper();
// 在使用前配置ObjectMapper的属性
static {
// 忽略json中存在但是java对象中不存在的属性,默认行为是报错
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 忽略java对象中值为null的属性,默认行为是序列化该属性
MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 按字母顺序升序排序,默认是按照字段在类中出现的顺序排序
// MAPPER.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
}
// 2. 将java对象转换为一个json字符串
public static String toJson(Object obj) {
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
// 3. 将json字符串转换为一个java对象
public static <T> T toObj(String jsonStr, Class<T> clazz) {
try {
return MAPPER.readValue(jsonStr, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
// 4. 将数组类型的json字符串转换为一个list
public static <T> ArrayList<T> toList(String jsonStr, Class<T> clazz) {
try {
return MAPPER.readValue(jsonStr, new TypeReference<ArrayList<T>>() {});
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
// 4. 将json字符串转换为一个map
public static <K, V> HashMap<K, V> toMap(String jsonStr, Class<K> keyClazz, Class<V> valueClazz) {
try {
return MAPPER.readValue(jsonStr, new TypeReference<HashMap<K, V>>() {});
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
// 5. 将json字符串转换为带有泛型信息的对象,这是把上一个方法中的TypeReference提到了方法参数中,
// 这里理解为上面toList方法的原始版
public static <T> T toObj(String json, TypeReference<T> typeReference) {
try {
return MAPPER.readValue(json, typeReference);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
第三步:测试jackson的基本功能
public class Demo1JacksonTest {
// 对象转json
@Test
public void test1() {
Person person = new Person(1L, "aaa", 18);
String s = JacksonUtil.toJson(person);
System.out.println("s = " + s);
}
// json转对象
@Test
public void test2() {
String json = "{\"id\":1,\"name\":\"aaa\",\"age\":20, \"k1\": 2}";
Person obj = JacksonUtil.toObj(json, Person.class);
System.out.println("obj = " + obj);
}
// 解析数组字符串
@Test
public void test3() {
String json = "[{\"id\":1,\"name\":\"aaa\",\"age\":20}, {\"id\":2,\"name\":\"bbb\",\"age\":21}]";
ArrayList<Person> list = JacksonUtil.toList(json, Person.class);
System.out.println("list = " + list);
}
// 解析json到map中
@Test
public void test5() {
String json = "{\"李四\":{\"id\":2,\"name\":\"李四\",\"age\":20},\"张三\":{\"id\":1,\"name\":\"张三\",\"age\":20},\"王五\":{\"id\":3,\"name\":\"王五\",\"age\":20}}";
Map<String, Person> map = JacksonUtil.toMap(json, String.class, Person.class);
System.out.println("map = " + map);
}
}
第四步:测试在多线程环境下使用jackson。启动1万个线程,共有一个ObjectMapper,每个线程都执行一次json的转换,观察是否线程安全。
public class Demo1JacksonTest2 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
long l = System.currentTimeMillis();
Human person = new Human(1L, "aaa", 18, l);
String json = JacksonUtil.toJson(person);
Human obj = JacksonUtil.toObj(json, Human.class);
System.out.println(Thread.currentThread().getName() + " " + l + " " + obj);
assert obj.getCreateTime() == l;
});
list.add(thread);
thread.start();
}
for (Thread thread : list) {
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms"); // 5399ms,如果为每个线程创建一个jackson实例,同样的代码,需要运行51166ms,相差近10倍
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Human {
private Long id;
private String name;
private Integer age;
private Long createTime;
}
总结:jackson是线程安全的,并且多个线程共用一个ObjectMapper的性能更好,注意在运行期间不可以修改jackson的配置。
高级特性
这里只学习jackson的基本功能,对于其它,例如从输入流中读取json、在实体类添加通过注解(@JsonProperty、@JsonIgnore)来配置jackson的解析行为、属性过滤、树模型、模块等,这里暂不涉及,因为实际开发中一般不要求把这么复杂的逻辑放在json转换中来做,通常要求通过java代码来完成上述功能。
gson
google开源的基于java语言的json解析框架,
基本使用
第一步:添加依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
第二步:编写gson的工具类
public class GsonUtil {
// 1、创建全局共享的实例,Gson对象是线程安全的,但是要注意,不要运行期间动态修改它的配置
private static final Gson GSON = new Gson();
// 2、gson的基本使用
public static String toJson(Object obj) {
return GSON.toJson(obj);
}
public static <T> T toObj(String json, Class<T> clazz) {
return GSON.fromJson(json, clazz);
}
public static <T> ArrayList<T> toList(String json, Class<T> clazz) {
// 注意要导入 com.google.gson.reflect.TypeToken,不要使用guava库中的
return GSON.fromJson(json, new TypeToken<ArrayList<T>>() {}.getType());
}
}
完成。这里就不展示测试代码了,和jackson几乎相同
高级特性
gson中提供的高级特性,包括 自定义序列化反序列化、版本控制、流式API(适合处理大json文件,它不需要一次把所有json数据读取到内存中,比基于反射的API快很多)。对于这些高级特性,这里同样不做介绍,这里只关注最基本的使用。
fastjson
阿里开源的json解析工具,以性能快著称,但是出现过安全漏洞和bug,尽量使用最新版
基本使用
第一步:添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<!--不要使用1.x,有安全漏洞-->
<version>2.0.53</version>
</dependency>
第二步:编写基于fastjson的工具类
public class FastJsonUtil {
// 基本使用,通过JSON对象来使用fastjson
public static String toJson(Object obj) {
return JSON.toJSONString(obj);
}
public static <T> T toObj(String jsonStr, Class<T> clazz) {
return JSON.parseObject(jsonStr, clazz);
}
public static <T> T toObj(String jsonStr, TypeReference<T> type) {
return JSON.parseObject(jsonStr, type);
}
public static <T> List<T> toList(String jsonStr, Class<T> clazz) {
return JSON.parseArray(jsonStr, clazz);
}
}
性能对比
以在学习jackson时的第四步为例,jackson、gson、fastjson,执行同样的代码,比较它们的执行效率。启动1万个线程,每个线程都执行相同的序列化和反序列化任务,比较它们的执行时间。
框架 | 1万个线程,执行基本的json转换任务 |
---|---|
jackson | 6168ms |
gson | 23975ms |
fastjson | 12240ms |
可以发现,jackson是最快的,所以jackson更加适合日常使用
实战案例
案例1
需求:用户给我一个字符串类型的集合,集合中存储了字段名称,我只需要返回这些名称对应的字段,相当于动态属性过滤,该如何实现?
方案1:使用map动态构建返回的数据
这个最简单了,这里不做演示了。
方案2:使用jackson的过滤器
第一步:在实体类上添加过滤器
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonFilter("userFilter") // 过滤器
public class Human {
private Long id;
private String name;
private Integer age;
private Long createTime;
}
第二步:编写转换方法,因为需要修改ObjectMapper的配置,所以创建一个新的实例,不和其他任务共用
public static String toJsonWithFilterFields(Human obj, Set<String> fieldNameSet) {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("userFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fieldNameSet));
try {
return mapper.writer(filterProvider).writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
第三步:测试,成功,只转换了我们指定的字段
@Test
public void test9() {
Human human = new Human(1L, "aaa", 18, System.currentTimeMillis());
String json = JacksonUtil.toJsonWithFilterFields(human, Sets.newHashSet("id", "name", "age"));
System.out.println("json = " + json); // json = {"id":1,"name":"aaa","age":18}
}
方案3:使用gson
第一步:编写转换方法,同样,字段过滤,需要单独配置gson实例
public static Person toJsonWithFilterField(String json, Class<Person> clazz, HashSet<String> fields) {
Gson gson = new GsonBuilder()
.serializeNulls()
.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
if (CollectionUtils.isEmpty(fields)) {
return false;
}
// 只保留指定名称的字段
return !fields.contains(f.getName());
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
return gson.fromJson(json, clazz);
}
第二步:测试
@Test
public void test4() {
Human human = new Human(1L, "aaa", 18, System.currentTimeMillis());
String json = GsonUtil.toJsonWithFilterField(human, Sets.newHashSet("id", "age"));
System.out.println("json = " + json); // {"id":1,"age":18}
}