java json解析工具对比

简介

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转换任务
jackson6168ms
gson23975ms
fastjson12240ms

可以发现,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}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值