Featured image of post Json处理工具库

Json处理工具库

FastJSON,Gson,Jackson等类库的使用以及优缺点

FastJson

简介

  • FastJson是阿里巴巴公司提供的一个用Java语言编写的高性能功能完善的JSON库,该库涉及的最基本功能就是序列化和反序列化。
  • Fastjson支持java bean的直接序列化,同时也支持集合、Map、日期、Enum和泛型等的序列化。
  • 你可以使用com.alibaba.fastjson.JSON这个类进行序列化和反序列化,常用的序列化操作都可以在JSON类上的静态方法直接完成。
  • Fastjson采用独创的算法,将parse的速度提升到极致,号称超过所有Json库。而且,使用Fastjson解析时,除了需要使用Fastjson所提供的jar包外,再不需要额外的jar包,就能够直接跑在JDK上

原理

优势

  • 快速FAST(比任何一款都快)
  • 面向对象
  • 功能强大(支持普通JDK类任意java bean Class,Collection,Map,Date或者enum)
  • 零依赖(只需要有JDK即可)
  • 支持注解,全类型序列化

缺点:FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用

代码示例

  1. maven依赖
    <!-- FastJson将Json转换Bean -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.46</version>
    </dependency>
  1. bean转换json
  • 将对象转换成格式化的json
JSON.toJSONString(obj,true);
  • 将对象转换成非格式化的json
JSON.toJSONString(obj,false);
  • 复杂类型对象

对于复杂类型的转换,对于重复的引用在转成json串后在json串中出现引用的字符,比如 ref":"[0].books[1]

Student stu = new Student();
Set books= new HashSet();
Book book = new Book();
books.add(book);
stu.setBooks(books);
List list = new ArrayList();
for(int i=0;i<5;i++)
list.add(stu);
String json = JSON.toJSONString(list,true);
  1. json转换bean
String json = "{\"id\":\"2\",\"name\":\"Json技术\"}";
Book book = JSON.parseObject(json, Book.class);
  1. json转换复杂的bean,比如List,Map
String json = "[{\"id\":\"1\",\"name\":\"Json技术\"},{\"id\":\"2\",\"name\":\"java技术\"}]";
//将json转换成List
List list = JSON.parseObject(json,new TypeReference<ARRAYLIST>(){});
//将json转换成Set
Set set = JSON.parseObject(json,new TypeReference<HASHSET>(){});
  1. 通过json对象直接操作json
  • 从json串中获取属性
  String propertyName = 'id';
  String propertyValue = "";
  String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
  JSONObject obj = JSON.parseObject(json);
  propertyValue = obj.get(propertyName));
  • 除去json中的某个属性
  String propertyName = 'id';
  String propertyValue = "";
  String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
  JSONObject obj = JSON.parseObject(json);
  Set set = obj.keySet();
  propertyValue = set.remove(propertyName);
  json = obj.toString();
  • 向json中添加属性
  String propertyName = 'desc';
  Object propertyValue = "json的玩意儿";
  String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
  JSONObject obj = JSON.parseObject(json);
  obj.put(propertyName, JSON.toJSONString(propertyValue));
  json = obj.toString();
  • 修改json中的属性
  String propertyName = 'name';
  Object propertyValue = "json的玩意儿";
  String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
  JSONObject obj = JSON.parseObject(json);
  Set set = obj.keySet();
  if(set.contains(propertyName))
  obj.put(propertyName, JSON.toJSONString(propertyValue));
  json = obj.toString();
  • 判断json中是否有属性
  String propertyName = 'name';
  boolean isContain = false;
  String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
  JSONObject obj = JSON.parseObject(json);
  Set set = obj.keySet();
  isContain = set.contains(propertyName);
  • json中日期格式的处理
  Object obj = new Date();
  String json = JSON.toJSONStringWithDateFormat(obj, "yyyy-MM-dd HH:mm:ss.SSS");

使用JSON.toJSONStringWithDateFormat,该方法可以使用设置的日期格式对日期进行转换

Gson

简介

Google提供的用来java对象和JSON数据之间进行映射的JAVA类库,可以将一个JSON字符转成一个java对象,反过来也OK

原理

Gson原理

优势

  • 快速,高效
  • 代码量少
  • 面向对象
  • 数据传输解析方便

代码示例

  1. 主要类介绍
  • Gson类:解析json的最基础的工具类
  • JsonParser类:解析器来解析JSON到JsonElements的解析树
  • JsonElement类:一个类代表的JSON元素
  • JsonObject类:JSON对象类型
  • JsonArray类:JsonObject数组
  • TypeToken类:用于创建type,比如泛型List
  1. maven依赖
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.0</version>
    </dependency>
  1. bean转换json
Gson gson = new Gson();
String json = gson.toJson(obj);
//obj是对象
  1. json转换bean
Gson gson = new Gson();
String json = "{\"id\":\"2\",\"name\":\"Json技术\"}";
Book book = gson.fromJson(json, Book.class);
  1. json转换复杂bean(含有List,Set),需使用TypeToken
Gson gson = new Gson();
String json = "[{\"id\":\"1\",\"name\":\"Json技术\"},{\"id\":\"2\",\"name\":\"java技术\"}]";
//将json转换成List
List list = gson.fromJson(json,new TypeToken<LIST>() {}.getType());
//将json转换成Set
Set set = gson.fromJson(json,new TypeToken<SET>() {}.getType());
  1. 直接操作json
  • 格式化Json
String json = "[{\"id\":\"1\",\"name\":\"Json技术\"},{\"id\":\"2\",\"name\":\"java技术\"}]";
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(json);
json = gson.toJson(je);
  • 判断是否是json
String json = "[{\"id\":\"1\",\"name\":\"Json技术\"},{\"id\":\"2\",\"name\":\"java技术\"}]";
boolean jsonFlag;
try {
	new JsonParser().parse(str).getAsJsonObject();
	jsonFlag = true;
} catch (Exception e) {
	jsonFlag = false;
}
  • 从json获取数据
String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
String propertyName = 'id';
String propertyValue = "";
try {
	JsonParser jsonParser = new JsonParser();
	JsonElement element = jsonParser.parse(json);
	JsonObject jsonObj = element.getAsJsonObject();
	propertyValue = jsonObj.get(propertyName).toString();
} catch (Exception e) {
	propertyValue = null;
}
  • 除去json中的属性
String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
String propertyName = 'id';
JsonParser jsonParser = new JsonParser();
JsonElement element = jsonParser.parse(json);
JsonObject jsonObj = element.getAsJsonObject();
jsonObj.remove(propertyName);
json = jsonObj.toString();
  • 向json中添加属性
String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
String propertyName = 'desc';
Object propertyValue = "json各种技术的调研";
JsonParser jsonParser = new JsonParser();
JsonElement element = jsonParser.parse(json);
JsonObject jsonObj = element.getAsJsonObject();
jsonObj.addProperty(propertyName, new Gson().toJson(propertyValue));
json = jsonObj.toString();
  • 修改json中的属性
String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
String propertyName = 'name';
Object propertyValue = "json各种技术的调研";
JsonParser jsonParser = new JsonParser();
JsonElement element = jsonParser.parse(json);
JsonObject jsonObj = element.getAsJsonObject();
jsonObj.remove(propertyName);
jsonObj.addProperty(propertyName, new Gson().toJson(propertyValue));
json = jsonObj.toString();
  • 判断json中是否有属性
String json = "{\"id\":\"1\",\"name\":\"Json技术\"}";
String propertyName = 'name';
boolean isContains = false ;
JsonParser jsonParser = new JsonParser();
JsonElement element = jsonParser.parse(json);
JsonObject jsonObj = element.getAsJsonObject();
isContains = jsonObj.has(propertyName);
  • json中日期格式的处理
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Gson gson = builder.create();

然后使用gson对象进行json的处理,如果出现日期Date类的对象,就会按照设置的格式进行处理

  • json中对于Html的转义
Gson gson = new Gson();

这种对象默认对Html进行转义,如果不想转义使用下面的方法

GsonBuilder builder = new GsonBuilder();
builder.disableHtmlEscaping();
Gson gson = builder.create();

注意:

  • 内部嵌套的类必须是static的,要不然解析会出错;
  • 类里面的属性名必须跟Json字段里面的Key是一模一样的;
  • 内部嵌套的用[]括起来的部分是一个List,所以定义为 public List< B> b,而只用{}嵌套的就定义为 public C c,是不是很简单,而且现在android studio上可以直接用插件生成实体类,那更加方便了

Jackson

简介

Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json、xml转换成Java对象,是SpringBoot默认序列化Json库

原理

  • 基于事件驱动,与GSON相同,先创建一个对应于JSON数据的JavaBean类就可以通过简单的操作解析出所需JSON数据。
  • 和Gson解析不同的是,GSON可按需解析,即创建的JavaBean类不一定完全涵盖所要解析的JSON数据,按需创建属性,但Jackson解析对应的JavaBean必须把Json数据里面的所有key都有所对应,即必须把JSON内的数据所有解析出来,无法按需解析。
  • Jackson的解析速度和效率都要比GSON高

优势

  • 解析效率最高
  • 在数据量大的情况优势尤为明显、占存少
  • 易于使用:-Jackson API提供了高级外观,以简化常用使用。
  • 无需创建映射:Jackson API为大多数序列化的对象提供了默认映射。
  • 性能:Jackson轻便快速,内存占用少,适用于大型对象图或系统。
  • 干净的JSON:Jackson创建了简洁的JSON,易于阅读。
  • 没有依赖关系:Jackson库除JDK以外不需要任何其他库。
  • 开源:Jackson库是开源的,可以免费使用

缺点:必须完全解析文档,如果要按需解析的话可以拆分Json来读取,操作和解析方法复杂,Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题

推荐使用场景:适用于需要处理超大型JSON文档、不需要对JSON文档进行按需解析、、性能要求较高的场合

代码示例

  1. 依赖引入
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.6</version>
</dependency>
<!-- 也可以使用下面的方式 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
</dependency>
  1. json转换bean
ObjectMapper objectMapper = new ObjectMapper();
String carJson ="{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";
try {
		Car car = objectMapper.readValue(carJson, Car.class);
		System.out.println("car brand = " + car.getBrand());
		System.out.println("car doors = " + car.getDoors());
} catch (IOException e) {
		e.printStackTrace();
}

ObjectMapper如何匹配JSON对象的字段和Java对象的属性 :

默认情况下,Jackson通过将JSON字段的名称与Java对象中的getter和setter方法进行匹配,将JSON对象的字段映射到Java对象中的属性。

Jackson删除了getter和setter方法名称的“ get”和“ set”部分,并将其余名称的第一个字符转换为小写

如果需要以其他方式将JSON对象字段与Java对象字段匹配,则需要使用自定义序列化器和反序列化器,或者使用一些Jackson注解

  1. JSON 字符输入流–>Java对象
String json = "{\"id\":\"lai\",\"name\":\"程序\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
  WeChat weChat = mapper.readValue(json, WeChat.class);
  System.out.println(weChat);
} catch (JsonProcessingException e) {
  log.error("解析异常", e);
}
  1. Java对象–>json
WeChat weChat = new WeChat();
weChat.setId("lai");
weChat.setName("程序");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

ObjectMapper mapper = new ObjectMapper();
try {
  String result = mapper.writeValueAsString(weChat);
  System.out.println(result);
} catch (JsonProcessingException e) {
  log.error("转换异常", e);
}
  1. JSON –> Map
String json = "{\"id\":\"lai\",\"name\":\"程序\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
  // 对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
  Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
  });
  System.out.println(map);
} catch (JsonProcessingException e) {
  log.error("解析异常", e);
}

示例使用文件URL,也可以使用HTTP URL(类似于http://jenkov.com/some-data.json)

  1. Java对象–>文件
WeChat weChat = new WeChat();
weChat.setId("lai");
weChat.setName("程序");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
  //写到文件
  mapper.writeValue(new File("/json.txt"), weChat);
  //从文件中读取
  WeChat weChat1 = mapper.readValue(new File("/json.txt"), WeChat.class);
  System.out.println(weChat1);
} catch (IOException e) {
  log.error("转换异常", e);
}
  1. Java对象–>字节流
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程序新视界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
  // 写为字节流
  byte[] bytes = mapper.writeValueAsBytes(weChat);
  // 从字节流读取
  WeChat weChat1 = mapper.readValue(bytes, WeChat.class);
  System.out.println(weChat1);
} catch (IOException e) {
  log.error("转换异常", e);
}
  1. 树模型

如果Json字符串比较大,则可使用JSON树模型来灵活的获取所需的字段内容。在Jackson中提供了get、path、has等方法来获取或判断

// 构建JSON树
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.put("id", "lai");
        root.put("name", "程序");
        ArrayNode interest = root.putArray("interest");
        interest.add("Java");
        interest.add("Spring Boot");
        interest.add("JVM");
        // JSON树转JSON字符串
        String json = null;
        try {
            json = mapper.writeValueAsString(root);
        } catch (JsonProcessingException e) {
            log.error("Json Node转换异常", e);
        }
        System.out.println(json);

//解析JSON字符串为JSON树模型
String json = "{\"id\":\"lai\",\"name\":\"程序\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 将JSON字符串转为JSON树
            JsonNode jsonNode = mapper.readTree(json);
            String name = jsonNode.path("name").asText();
            System.out.println(name);

            JsonNode interestNode = jsonNode.get("interest");
            if (interestNode.isArray()){
                for (JsonNode node : interestNode){
                    System.out.println(node.asText());
                }
            }
        } catch (JsonProcessingException e) {
            log.error("Json Node转换异常", e);
        }

其中get方法和path功能相似,区别在于如果要读取的key在Json串中不存在时,get方法会null,而path会返回MissingNode实例对象,在链路方法情况下保证不会抛出异常

  1. 格式化统一配置
//反序列化时忽略json中存在但Java对象不存在的属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化时日期格式默认为yyyy-MM-dd'T'HH:mm:ss.SSSZ
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//序列化时自定义时间日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//序列化时忽略值为null的属性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化时忽略值为默认值的属性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
  1. 注解

某些个别的场景下,需要针对具体的字段进行配置,这就需要用注解。

比如当Json字符串中的字段与Java对象中的属性不一致时,就需要通过注解来建立它们直接的关系

  • @JsonProperty,作用JavaBean字段上,指定一个字段用于JSON映射,默认情况下映射的JSON字段与注解的字段名称相同。可通过value属性指定映射的JSON的字段名称。

  • @JsonIgnore可用于字段、getter/setter、构造函数参数上,指定字段不参与序列化和反序列化。

  • @JsonIgnoreProperties作用于类上,序列化时@JsonIgnoreProperties({"prop1", "prop2"})会忽略pro1和pro2两个属性。反序列化时@JsonIgnoreProperties(ignoreUnknown=true)会忽略类中不存在的字段。

  • @JsonFormat作用于字段上,通常用来进行格式化操作。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;

如果JavaBean中的时间字段使用的是JDK8新增的时间日期(LocalDate/LocalTime/LocalDateTime)类型的话,需要添加jackson-datatype-jsr310依赖。在讲依赖部分时,SpringBoot默认引入的依赖中就有这个。

当然,还有一些其他的注解,比如@JsonPropertyOrder@JsonRootName@JsonAnySetter@JsonAnyGetter@JsonNaming等,当使用时参考对应的文档和示例看一下就可以,这里就不再一一列出了

  1. 自定义解析器
public class MyFastjsonDeserialize extends JsonDeserializer<Point> {
    @Override
    public Point deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        Iterator<JsonNode> iterator = node.get("coordinates").elements();
        List<Double> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next().asDouble());
        }
        return new Point(list.get(0), list.get(1));
    }
}

需要注册到Mapper中:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Point.class, new MyFastjsonDeserialize());
objectMapper.registerModule(module);

jackson还可以处理xml,api很相似,这部分略过

Spring Boot 集成

#指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
spring.jackson.date-format 
#是否开启Jackson的反序列化
spring.jackson.deserialization
#是否开启json的generators.
spring.jackson.generator
#指定Joda date/time的格式,比如yyyy-MM-ddHH:mm:ss). 如果没有配置的话,dateformat会作为backup
spring.jackson.joda-date-time-format
#指定json使用的Locale.
spring.jackson.locale
#是否开启Jackson通用的特性.
spring.jackson.mapper
#是否开启jackson的parser特性.
spring.jackson.parser
#指定PropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)或者指定PropertyNamingStrategy子类的全限定类名.
spring.jackson.property-naming-strategy
#是否开启jackson的序列化.
spring.jackson.serialization
#指定序列化时属性的inclusion方式,具体查看JsonInclude.Include枚举.
spring.jackson.serialization-inclusion
#指定日期格式化时区,比如America/Los_Angeles或者GMT+10.
spring.jackson.time-zone
  • 也可以使用手动配置
@Configuration
public class JacksonConfig {
    @Bean
    @Qualifier("json")
    public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper mapper = builder.createXmlMapper(false)
                .build();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}

使用时直接注入即可

@Resource
private ObjectMapper jsonMapper;

ObjectMapper是线程安全的

对比

  • fastjsonjackson 在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法
  • Gson 是通过反射遍历该类中的所有属性。在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success
  • 把Java对象JSON序列化,Jackson速度最快,在测试中比Gson快接近50%,FastJSON和Gson速度接近
  • 把JSON反序列化成Java对象,FastJSON、Jackson速度接近,Gson速度稍慢,不过差距很小
  • 性能方面,Jackson和FastJson差距很小,Jackson是SpringBoot默认的序列化库,也是最稳定的一个,FastJson由于频繁被曝出漏洞且作者没有那么多精力维护,所以默认序列方式还是选择Jackson最好
版权所有,转载请注明出处!
渝ICP备2022006471
Built with Hugo
主题 StackJimmy 设计