前言
在java中进行json操作其实并不是一个新的话题,我们已经这样使用了很多年,但是一直以来,缺少标准。不过现在有了这个标准(其实有了几年了),我们晚点再说新的标准。在我的职业上生涯里,对json处理基本上就是fastjson、gson和jackson这样三个库占了主要地位。其中,fastjson多年前网传是最快的,我没测过。gson可能是用的最多的吧,因为操作简单,需要的配置少。jackson其实很多经验不足的人是不认识它的,但是抄帖子的时候其实见过这个名字,因为在springweb的框架里,它是默认的处理接口json序列化反序列化的框架,有的时候我们需要改它的配置就会见到它的身影,比如时间的序列化格式。
为什么现在讨论这件事
其实,这些使用场景对于我的编程模型并没有多少改变,因为接口的json会被处理为java对象再被使用,其它地方也基本是这样,至少最近我用了postgresql。这倒不是说它带来了多大的问题,恰恰相反,它给我带来了便利。它有个jsonb字段。我可以把异构对象直接用json结构存进去,同时还可以针对字段建立索引,不要太方便。但是,他也给我带来了挑战。我用mybatis做dao层操作框架,json对象好存,不好读呀。我该用什么实体类型读取它呢?由于最早期这部分不是我写的,他们选择了直接使用我们操作json框架(fastjson)里的JSONObject作为实体属性来操作,而且基于此写了不少代码。但是,这个JSONObject是非标的,它属于fastjson,其它的框架并不会使用这个数据结构。最近我在调试代码的时候就动了这个心思。有没有java标准的json数据结构和操作结构呢?如果有,那么它就会被未来的各种库所兼容,我也可以没有顾忌的将它引入到我的基础数据结构中,就像是map和hashmap。
JAVA的json标准
这个问题的答案是,有的,而且,有了几年了。由于时间原因和英文基础,没有去查英文的原文档,而是搜了一些简单地资料。归结下来是,有这样几个标准:JSR-353,JSR-374,JSR-367。而相关的java标准接口出现在了java ee 8中。注意,是JAVA EE而不是JAVA SE。其实最早在JAVA EE7 中就有了,那个时候是JSON-P接口,而在JAVA EE8中则变成了JSON-B。他们和JSR的对应关系我暂时就不理了,总是,现在是有标准的。它可以通过以下的maven依赖被引入,对的,它不在java的核心包中:
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>1.0.2</version>
</dependency>
可以看到坐标里面写的是jakarta。这让我想起来了,javaee开源了,名字叫做jakarta,所以如果你找的是javax相关的包,会看到它有几年没更新了。
实现
兜兜转转,还是找到了一些权威的说明网站,这里列举两个比较重要的:
- java ee的github: https://javaee.github.io/jsonb-spec/getting-started.html
- ibm的说明(全中文,很详尽): https://www.ibm.com/developerworks/cn/java/j-javaee8-json-binding-1/index.html
- Jakarta JSON Binding (JSON-B)官网:http://json-b.net/
关于引入的事情,在javaee的gibhub上说得很详细,只不过都是英文的罢了,这里我就只写依赖了(版本更新到了目前最新):
<!-- JSON-P -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>1.1.6</version>
<scope>runtime</scope>
</dependency>
<!-- JSON-B API -->
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>1.0.2</version>
</dependency>
<!-- Yasson (JSON-B implementation) -->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>1.0.6</version>
<scope>runtime</scope>
</dependency>
尝试
接下来,我会对这一套内容进行一些基本操作的尝试,以确保它基本是满足我的需求的。
基本的json对象序列化和反序列化
这里我设计了一个基本的测试,下面是实体对象和序列化反序列化代码:
/**
* 用来辅助json测试的对象
*/
public class JsonTestObject {
//int
private Integer intValue;
//short
private Short shortValue;
//long
private Long longValue;
//String
private String strValue;
//char
private char charValue;
//byte
private byte byteValue;
//byteArray
private Byte[] byteArray;
//double
private Double doubleValue;
//date
private Date dateValue;
……getter and setter
}
@Test
public void testSerialize(){
JsonTestObject jsonTestObject=new JsonTestObject();
jsonTestObject.setIntValue(Short.MAX_VALUE+1);
jsonTestObject.setShortValue((short) 1000);
jsonTestObject.setLongValue(Integer.MAX_VALUE+1l);
jsonTestObject.setStrValue("这是字符串");
jsonTestObject.setCharValue('字');
jsonTestObject.setByteValue((byte) 0x33);
jsonTestObject.setByteArray(new Byte[]{0x33,0x34,0x09});
jsonTestObject.setDoubleValue(3.141592653);
jsonTestObject.setDateValue(Calendar.getInstance().getTime());
Jsonb jsonb = JsonbBuilder.create();
String result = jsonb.toJson(jsonTestObject);
logger.debug("json序列化结果:"+result);
JsonTestObject desrializeObject=jsonb.fromJson(result,JsonTestObject.class);
logger.debug("结果:"+jsonb.toJson(desrializeObject));
}
上面可以看到我们对各种基本类型都做了序列化和反序列化的测试,序列化的字符串如下(结果里没有格式化,我自己格式化的):
{
"byteArray": [51, 52, 9],
"byteValue": 51,
"charValue": "字",
"dateValue": "2020-04-07T10:21:24.947Z[UTC]",
"doubleValue": 3.141592653,
"intValue": 32768,
"longValue": 2147483648,
"shortValue": 1000,
"strValue": "这是字符串"
}
其实,这里我最大的惊喜就是,它对byteArray的序列化。之前我使用fastjson序列化过,反序列化回来就变成了字符串,这里是序列化成了整数数组,让我感到很惊讶,这确实是个很好的解决方法。
日期序列化格式的修改
这个和其他的框架的操作是类似的,对序列化操作实例进行配置即可,我的代码如下:
JsonbConfig jsonbConfig=new JsonbConfig();
jsonbConfig.withDateFormat("yyyy-MM-dd HH:mm:ss",null);
Jsonb jsonb = JsonbBuilder.create(jsonbConfig);
之前的创建是未使用配置类的,其结果就是使用了默认的配置,我们这里对默认配置进行了修改,这样就可以了。如果还有其它的修改,也在这里配置然后再用来创建Jsonb即可。
对于无属性的序列化和反序列化默认行为配置
在序列化的时候,我们可能需要对为null的对象进行序列化,即没有赋值,但是我有这个属性。有些时候,这样才是正确的,或者反过来。总之,这个配置的调整是很常见的。
jsonbConfig.withNullValues(false);
这个设置起来也是很简单。其实,大多数的配置都可以在JsonbConfig的方法中找到对应的方法。
将Long序列化为String
这个需求是在和前端进行对接的时候遇到的。js里面并没有Long这么长的整数,如果硬传过去,低三位会被置为零。为了解决这个问题,我们把long转化为String传出去。之前这个序列化问题都是使用Jackson配置的,在springboot的上,因为别的地方并不需要。
Jsonb中解决这个问题使用的是一个叫做Adapter的概念。将序列化和反序列化进行结对。我的代码如下:
/**
* Long类型适配器
* 在js中,无法解析完整的Long长度的整数,需要将其序列化为字符串后进行解析
*/
public class LongAdapter implements JsonbAdapter<Long,String> {
@Override
public String adaptToJson(Long obj) throws Exception {
return String.valueOf(obj);
}
@Override
public Long adaptFromJson(String obj) throws Exception {
return Long.valueOf(obj);
}
}
这就是为了在Long和String之间转换的Adapter类。它需要实现JsonbAdapter接口。下面是配置使用的代码:
jsonbConfig.withAdapters(new LongAdapter());
这个是负责接收类型为Long,json中类型为String的相互转换的,对于Long对Long的转换并没有实际影响。
集合反序列化
这个功能是没有特殊的API的,不像fastjson里有个反序列化Array的方法,这里还是通过fromJson来做的
List<BaseJsonTest> result=JsonUtil.getJsonb().fromJson(jsonStr,new ArrayList<BaseJsonTest>(){}.getClass().getGenericSuperclass());
可以看到,获取类型的代码有点长,有兴趣可以自己封装下。另外,经过我测试,如果反序列化类型直接为ArrayList,会被创建为HashMap,并执行成功,尽管类型不匹配。然后在调用起对象的API时报错找不到方法。这里似乎没有继承类的概念,所以也不要想着你写个基类它给你返回一个派生类。不过吧,有兴趣可以自己弄个Adapter实现类似的事情。具体原理我还没想好,难点应该是在子类的选择上。如果集合里只有一个类,那就直接反序列化成对应的类型就行了。
小结
至此,基本的json操作就差不多全了,也和常见的操作类的功能差不多了。后面的就是自己封装的扩展了,以后积累到一定程度再说吧。
注:2018-03-05日,开源组织Eclipse基金会宣布将JavaEE(Enterprise Edition)被更名为JakartaEE(雅加达)。这是Oracle将Java移交给开源组织Eclipse后实现对Java品牌控制的最新举措。
网友评论