java json解析工具对比

简介

JSON:JavaScript Object Notation,JS 对象简谱,是JS对象的字符串表示法,它使用文本表示一个JS对象的信息,本质是一个字符串,后来被用作数据交换,所以也是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储和表示数据,简洁和清晰的层次结构使得JSON成为理想的数据交换语言。

JSON是一种特殊格式的字符串,包含六个构造字符、字符串、数字和三个字面名。

  • 六个构造字符: { } : ,
  • 字符串:是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义双引号
  • 三个字面量:false、true、null。
  • 数组:数组中的元素存放在中括号中,用逗号分割
  • 对象:对象中的内容是键值对,存放在大括号中,键值对之间由逗号分割,键和值之间由冒号分割

json字符串的案例:

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公司开源。

基本使用

第一步:添加依赖

xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>

第二步:编写jackson的工具类。我把jackson的基本使用都总结到了这个工具类中

java 复制代码
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的基本功能

java 复制代码
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的转换,观察是否线程安全。

java 复制代码
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解析框架,

基本使用

第一步:添加依赖

xml 复制代码
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>

第二步:编写gson的工具类

java 复制代码
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,尽量使用最新版

基本使用

第一步:添加依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <!--不要使用1.x,有安全漏洞-->
    <version>2.0.53</version>
</dependency>

第二步:编写基于fastjson的工具类

java 复制代码
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的过滤器

第一步:在实体类上添加过滤器

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonFilter("userFilter")  // 过滤器
public class Human {
    private Long id;
    private String name;
    private Integer age;
    private Long createTime;
}

第二步:编写转换方法,因为需要修改ObjectMapper的配置,所以创建一个新的实例,不和其他任务共用

java 复制代码
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);
    }
}

第三步:测试,成功,只转换了我们指定的字段

java 复制代码
@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实例

java 复制代码
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);
}

第二步:测试

java 复制代码
@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}
}
相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔1 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json