做难事必有所得
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/jackson-learning
目录
前言
jackson-core
是三大核心模块之一,并且它是核心中的核心,它提供了对JSON数据的完整支持。
此模块提供了最具底层的Streaming JSON解析器/生成器,这组流式API属于Low-Level API,具有非常显著的特点:
- 开销小,损耗小,性能极高
- 因为是Low-Level API,所以灵活度极高
- 又因为是Low-Level API,所以易错性高,可读性差
jackson-core
模块提供了两种处理JSON的方式(整个Jackson一共3种):
- 流式API:读取并将JSON内容写入作为离散事件 ->
JsonParser
读取数据,而JsonGenerator
负责写入数据 - 树模型:JSON文件在内存里以树形式表示。此种方式也很灵活,它类似于XML的DOM解析
本文将重点讲解流式API的使用:它是所有的三种方式中效率上最高的,当然也是最易出错、且最难使用的方式。
说明:树模型方式,将会放在和第三种处理方式:数据绑定 中一起讲述(数据绑定在另外一个核心模块jackson-databind中)
正文
我们知道,Jackson提供了一种对性能有极致要求的方式:流式API。它用于对性能有一定要求的场景,这个时候就可以使用此种方式来对JSON进行读写。
对于一般的读写(99.99%情况),我们使用最简单的databind方式即可,这部分在专栏对应章节会作为重中之重进行讲解
概念解释:流式、增量模式、JsonToken
- 流式(Streaming):此概念和Java8中的Stream流是不同的。这里指的是IO流,因此在读/写的最后都是需要close的哦
- 增量模式(incremental mode):它表示每个部分一个一个地往上增加,类似于垒砖。使用此流式API读写JSON的方式使用的均是增量模式,
- JsonToken:每一部分都是一个独立的Token(有不同类型的Token),最终被“拼凑”起来就是一个JSON。这是流式API里很重要的一个抽象概念。
Demo示例
下面将通过一个示例先看看效果,再基于此做更深入的探讨。
JsonGenerator 写
public static void main(String[] args) throws IOException {
JsonFactory factory = new JsonFactory();
// 此处最终输输出到OutputStreams输出流(此处输出到文件)
JsonGenerator jsonGenerator = factory.createGenerator(new File("java-jackson/src/main/resources/person.json"), JsonEncoding.UTF8);
jsonGenerator.writeStartObject(); //开始写,也就是这个符号 {
jsonGenerator.writeStringField("name", "YourBatman");
jsonGenerator.writeNumberField("age", 18);
// 写入Dog对象(枚举对象)
jsonGenerator.writeFieldName("dog");
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "旺财");
jsonGenerator.writeStringField("color", "WHITE");
jsonGenerator.writeEndObject();
//写入一个数组格式
jsonGenerator.writeFieldName("hobbies"); // "hobbies" :
jsonGenerator.writeStartArray(); // [
jsonGenerator.writeString("篮球"); // "篮球"
jsonGenerator.writeString("football"); // "football"
jsonGenerator.writeEndArray(); // ]
jsonGenerator.writeEndObject(); //结束写,也就是这个符号 }
// 关闭IO流
jsonGenerator.close();
}
提示:源码地址请参见专栏里每篇文章的文首部分
执行此程序,最终会在resources
目录下生成一个名为person.json
的文件,内容如下(多次执行,效果是覆盖):
完完全全的手动档有木有,逐字逐句的都得我们手动“书写”,格式也都得手动来控制。所以说这么做的灵活度极大,效率极高,但相反的便是编码效率低、代码可读性差,易出错…
JsonParser 读
public static void main(String[] args) throws IOException {
JsonFactory factory = new JsonFactory();
// 此处InputStream来自于文件
JsonParser jsonParser = factory.createParser(new File("java-jackson/src/main/resources/person.json"));
// 只要还没到末尾,也就是}这个符号,就一直读取
// {"name":"YourBatman","age":18,"dog":{"name":"旺财","color":"WHITE"},"hobbies":["篮球","football"]}
JsonToken jsonToken = null; // token类型
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jsonParser.getCurrentName();
if ("name".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
System.out.println(jsonParser.getText());
} else if ("age".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
System.out.println(jsonParser.getIntValue());
} else if ("dog".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String dogFieldName = jsonParser.getCurrentName();
if ("name".equals(dogFieldName)) {
jsonToken = jsonParser.nextToken();
System.out.println("======================token类型是:" + jsonToken);
System.out.println(jsonParser.getText());
} else if ("color".equals(dogFieldName)) {
jsonToken = jsonParser.nextToken();
System.out.println("======================token类型是:" + jsonToken);
System.out.println(jsonParser.getText());
}
}
} else if ("hobbies".equals(fieldname)) {
jsonToken = jsonParser.nextToken();
System.out.println("==============token类型是:" + jsonToken);
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
System.out.println(jsonParser.getText());
}
}
}
// 关闭IO流
jsonParser.close();
}
运行本程序,控制台里完美输出了各个属性的值:
==============token类型是:VALUE_STRING
YourBatman
==============token类型是:VALUE_NUMBER_INT
18
==============token类型是:START_OBJECT
======================token类型是:VALUE_STRING
旺财
======================token类型是:VALUE_STRING
WHITE
==============token类型是:START_ARRAY
篮球
football
核心API精讲
jackson-core内核模块里虽然有众多的类,但最为重要的只有如下3个:
JsonFactory
:Jackson主要的工厂方法,用于配置和构建解析器(JsonParser)和生成器(JsonGenerator),这个工厂实例是线程安全的,所以可以重复使用JsonGenerator
:用来生成Json格式的内容的(序列化)JsonParser
:读取Json格式的内容(返序列化,必须是Json格式)
Demo案例中介绍了使用Streaming API完成最常规、最基本的读/写操作,下面针对于本案例涉及到的几个核心API进行分析和讲解
JsonFactory
Jackson的主要工厂类,用于 配置和构建 JsonGenerator
和JsonParser
,这个工厂实例是线程安全的,因此可以重复使用。该工厂最主要的方法截图展示如下:
创建JsonParser:
创建JsonGenerator:
关于本工厂的配置和构建,使用源代码 + 注释的方式展示如下:
public class JsonFactory ... {
// 这三大Feature特征解析,会在专栏后面讲解特性配置章节中讲解
protected final static int DEFAULT_FACTORY_FEATURE_FLAGS = JsonFactory.Feature.collectDefaults();
protected final static int DEFAULT_PARSER_FEATURE_FLAGS = JsonParser.Feature.collectDefaults();
protected final static int DEFAULT_GENERATOR_FEATURE_FLAGS = JsonGenerator.Feature.collectDefaults();
...
// 你可以对输入流、输出流进行干预
// (这两个类均为抽象类,没有内置实现,若你有需要均需要你自己提供实现)
protected InputDecorator _inputDecorator;
protected OutputDecorator _outputDecorator;
...
// 构造器...
// 99%情况下,使用空构造即可
public JsonFactory() { this((ObjectCodec) null); }
// ObjectCodec编码器并没有内置实现类。一般情况下我们不会自己去书写编码器
// 提前透露只是:ObjectMapper便是一个(唯一一个)ObjectCodec实现类,它实现了自动绑定
public JsonFactory(ObjectCodec oc) { ... }
// 2.10版本新提供的基于Builder模式的构造方式,方便你进行配置
public JsonFactory(JsonFactoryBuilder b) { ... }
...
// 2.10新增静态方法:builder模式的体现(JsonFactoryBuilder是2.10版本新增的类)
public static TSFBuilder<?,?> builder() {
return new JsonFactoryBuilder();
}
...
// 拷贝出一个新的实例
public JsonFactory copy() { ... }
// 默认输出的字段无序。
// 若你是CSV或者Avro这种对字段顺序有要求的格式。请继承此类后然后把此属性改为true
@Override
public boolean requiresPropertyOrdering() { return false; }
@Override
public boolean canHandleBinaryNatively() { return false; }
public boolean canUseCharArrays() { return true; }
// 版本信息
@Override
public Version version() {
return PackageVersion.VERSION;
}
... // Configuration, factory features/parser features/generator features
}
该工厂的API整体上非常简单,JsonGenerator
和JsonParser
并不能自己new实例,而只能是通过这个工厂去创建,因此建议掌握本工厂的相关配置方法。
SPI(ServiceLoader)方式创建实例
上面介绍,JsonFactory
实例一般通过new构造函数的方式来创建一个工厂实例。作为如此优秀的Jackson库,自然考虑到了我们可能会有希望自己扩展JsonFactory
的需求,因此它还提供了一种更具弹性的SPI方式来创建工厂实例:允许我们通过配置文件的形式来动态调整使用的具体工厂
说明:关于SPI是何意思,以及JDK提供的
ServiceLoader
机制到底怎么玩,建议还不清楚的同学可以先补补课(我的免费博文里也有详细讲述,出门左拐)
基于SPI方式,上面Demo中创建JsonFactory
实例的方式可以改为:
public static void main(String[] args) throws IOException {
// 1、直接new的方式
// JsonFactory factory = new JsonFactory();
// 2、更具弹性的SPI方式
JsonFactory factory = null;
ServiceLoader<JsonFactory> load = ServiceLoader.load(JsonFactory.class);
Iterator<JsonFactory> it = load.iterator();
if (it.hasNext()) { // 此处是if不是while,因为我只需要一个而已
factory = it.next();
}
...
}
这样能正常运行,是因为jackson-core
这个包它内置了SPI的配置,如下截图:
使用自定义的JsonFactory
:
public class MyJsonFactory extends JsonFactory {
public MyJsonFactory(){
System.out.println("我是自定义的JsonFactory");
}
}
写个ServiceLoader的配置文件:
运行如上程序,可以看见控制台上有输出:
我是自定义的JsonFactory
...
由此可见我自定义的MyJsonFactory
最终被使用了,生效喽。
其实ServiceLoader.load()
的时候把MyJsonFactory
和系统内置的JsonFactory
都加载到了,只是最终只实例化了我的,这是由加载配置文件的顺序决定的,而这种顺序往往是不可控的~
因此需要注意:ServiceLoader它不像SpringFactoriesLoader
那样强大可以通过Order自己管理排序,so在实际使用中请务必做好相应的处理。
小建议:在实际代码书写中,若你想创建工厂实例,建议使用SPI方式,这样能让你的程序变得更富弹性
JsonToken
上面介绍了Streaming API中Token的含义,然而jackson-core里也提供了这样一个枚举类,枚举出了用于返回结果的一些token类型(也就是说当你遇上这些类型的token的时候,就可以去获取结果了)。
public enum JsonToken {
START_OBJECT("{", JsonTokenId.ID_START_OBJECT),
END_OBJECT("}", JsonTokenId.ID_END_OBJECT),
START_ARRAY("[", JsonTokenId.ID_START_ARRAY),
END_ARRAY("]", JsonTokenId.ID_END_ARRAY),
...
// 值的token类型
// 嵌入式的Object,表示raw Object
VALUE_EMBEDDED_OBJECT(null, JsonTokenId.ID_EMBEDDED_OBJECT),
VALUE_STRING(null, JsonTokenId.ID_STRING),
VALUE_NUMBER_INT(null, JsonTokenId.ID_NUMBER_INT),
VALUE_NUMBER_FLOAT(null, JsonTokenId.ID_NUMBER_FLOAT),
VALUE_TRUE("true", JsonTokenId.ID_TRUE),
VALUE_FALSE("false", JsonTokenId.ID_FALSE),
VALUE_NULL("null", JsonTokenId.ID_NULL);
}
JsonGenerator
它是用于writing JSON content的基类(抽象类),因为创建它的实例使用的是JsonFactory
工厂,因此我们无需关心具体实现类,只需了解此基类的API即可。
public abstract class JsonGenerator... {
// 用于漂亮格式的输出:便于人阅读。默认它是null:紧凑型输出
protected PrettyPrinter _cfgPrettyPrinter;
protected JsonGenerator() { }
@Override
public abstract Version version();
public JsonGenerator setPrettyPrinter(PrettyPrinter pp) { ... }
public abstract JsonGenerator useDefaultPrettyPrinter();
... // Feature configuration特性相关配置,在专栏特性文章里会详细介绍
// 获取当前值。它只用于高级数据绑定功能
public Object getCurrentValue() { ... }
public void setCurrentValue(Object v) { ... }
}
以上是此生成器的配置部分的源码说明,接下来便是它提供的写能力的核心API了(截图方式展示,更加的直观):
这些便是它最为重要的基础性API,使用起来没有难度,Demo示例中有相关代码展示。
约定:为了不显得文章过于臃肿,出现本末倒置现象而重点不突出,本文包括后续文章像这种基础性API的使用就不会给出相关示例,有任何疑问的可以留言~
write方法说明
从API中可以看出,这种Low-Level的API是只能写基本类型的:如int、long、BigInteger…对于对象类型如Date、Person等,它都是不能直接写的。
虽然JsonGenerator有个
writeObject(Object pojo)
方法,但默认情况下仅是调用了toString()方法而已,意义并不大。若想要良好的格式输出,需要自定义ObjectCodec
的实现~
所以说,针对于平时常说的Date/LocalDate类型是写为时间戳还是指定格式,这都是针对于高层API也就是ObjectMapper
而言的,毕竟它自己就是个ObjectCodec
嘛,所以实现了编码/解码过程。
而Streaming API只提供最为底层的、最为原子的方法,只有这样才能有最大的灵活性以及保证极致的性能
JsonParser
定义了一组API用于reading JSON content。它的实例也只能由工厂创建,所以也只需关心此基类的相关API即可:
public abstract class JsonParser ... {
private final static int MIN_BYTE_I = (int) Byte.MIN_VALUE; //-2的7次方 = -128
// 注意Byte.MAX_VALUE的最大值是127.而根据[JACKSON-804]规定把它扩大到了255(含)
private final static int MAX_BYTE_I = (int) 255;
private final static int MIN_SHORT_I = (int) Short.MIN_VALUE; // -2的15次方 = 32768
private final static int MAX_SHORT_I = (int) Short.MAX_VALUE; // 2的15次方-1 = 32767
// 内建支持的数字类型
public enum NumberType {
INT, LONG, BIG_INTEGER, FLOAT, DOUBLE, BIG_DECIMAL
};
...
public Object getCurrentValue() { ... }
public void setCurrentValue(Object v) { ... }
@Override
public abstract Version version();
// 2.9提供了NIO的支持。默认是没有开启的(向下兼容)
public boolean canParseAsync() { return false; }
public NonBlockingInputFeeder getNonBlockingInputFeeder() {
return null;
}
// 获取解析的当前上下文
public abstract JsonStreamContext getParsingContext();
// 关于token的操作(重要)
public JsonToken currentToken() { return getCurrentToken(); }
public int currentTokenId() { return getCurrentTokenId(); }
public abstract JsonToken getCurrentToken();
public abstract int getCurrentTokenId();
public abstract boolean hasTokenId(int id);
public abstract boolean hasToken(JsonToken t);
public boolean isExpectedStartArrayToken() { return currentToken() == JsonToken.START_ARRAY; }
public boolean isExpectedStartObjectToken() { return currentToken() == JsonToken.START_OBJECT; }
public abstract void clearCurrentToken();
public abstract JsonToken getLastClearedToken();
public abstract String getCurrentName() throws IOException;
public String currentName() throws IOException { return getCurrentName(); }
// 输入的第一个字符的位置
public abstract JsonLocation getTokenLocation();
// 该方法返回最后处理的字符的位置(一般用于error的时候打印日志)
public abstract JsonLocation getCurrentLocation();
// 这是最主要的迭代方法。它将推进流来确定下一个令牌的类型(如果有的话),若没有下一个了就返回null
public abstract JsonToken nextToken() throws IOException;
// 迭代方法,获取下一个值类型
public abstract JsonToken nextValue() throws IOException;
// 简答的说就是调用nextToken,然后判断这个token是否是JsonToken.FIELD_NAME类型;
// 并且getCurrentName()名称还和你指定的名称一样才会返回true
public boolean nextFieldName(SerializableString str) throws IOException { ... }
// 不解释
public String nextFieldName() throws IOException {
return (nextToken() == JsonToken.FIELD_NAME) ? getCurrentName() : null;
}
// nextToken + 获取值的 组合方法系列
public String nextTextValue() throws IOException {
return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
}
public int nextIntValue(int defaultValue) throws IOException {
return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
}
... // 省略Long、Bool类型的组合方法
// 该方法将跳过数组或的所有子标记当前指的对象
public abstract JsonParser skipChildren() throws IOException;
public void finishToken() throws IOException { ... }
public boolean isNaN() throws IOException {
return false;
}
}
相较于JsonGenerator
而言,它的API相对繁多。这是很容易理解的,毕竟反序列化一般都是比序列化麻烦很多的。
同样的,为了更直观的分类展示出核心API,下面还是以图示的方式列出:
这组数据读取API中,稍微不好理解的几个方法为:readValueAsXXX
系列方法。为了扫清困惑,下面专门针对它们附加一个示例以辅助理解
readValueAs()系列方法Demo示例
该方法将JSON内容反序列化为非容器类型(但可以是数组类型),通常是一个bean,一个数组或包装器类型(如Boolean)。Note:该系列方法依赖于ObjectCodec
对象编码/解码器
自定义实现一个ObjectCodec:
// 由于ObjectCodec需要实现的方法过多。本处只以一个实现为基准,各位举一反三即可
public class MyObjectCodec extends ObjectCodec {
@Override
public <T> T readValue(JsonParser jsonParser, Class<T> valueType) throws IOException {
// {"name":"YourBatman","age":18,"dog":{"name":"旺财","color":"WHITE"},"hobbies":["篮球","football"]}
// 使用JsonParser读取此json串,并且封装到valueType类型的JavaBean里
// 重点说明:次数实例理应通过valueType来构造,且赋值方面大量用到反射。
// 但本文仅想说明本质,因此不相关的步骤不在此处列出,各位知道便可
Person person = new Person();
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jsonParser.getCurrentName();
if ("name".equals(fieldname)) {
person.setName(jsonParser.nextTextValue());
} else if ("age".equals(fieldname)) {
person.setAge(jsonParser.nextIntValue(0));
} else if ("dog".equals(fieldname)) {
jsonParser.nextToken();
// 构造一个dog实例(同样的,实际场景是利用反射构造的哦)
Dog dog = new Dog();
person.setDog(dog);
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String dogFieldName = jsonParser.getCurrentName();
if ("name".equals(dogFieldName)) {
dog.setName(jsonParser.nextTextValue());
} else if ("color".equals(dogFieldName)) {
dog.setColor(Color.valueOf(jsonParser.nextTextValue()));
}
}
} else if ("hobbies".equals(fieldname)) {
jsonParser.nextToken();
List<String> hobbies = new ArrayList<>();
person.setHobbies(hobbies);
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
// hobbies.add(jsonParser.nextTextValue()); // 请注意:此处不能用next哦~
hobbies.add(jsonParser.getText());
}
}
}
return (T) person;
}
@Override
public Version version() {
return PackageVersion.VERSION;
}
// ... 省略其它方法
}
测试用例如下,它最关键的一句代码为jsonParser.setCodec(new MyObjectCodec())
,从而写的操作便委托给了对象编码/解码器去实现喽:
public static void main(String[] args) throws IOException {
JsonFactory factory = new JsonFactory();
// 此处InputStream来自于文件
JsonParser jsonParser = factory.createParser(new File("java-jackson/src/main/resources/person.json"));
jsonParser.setCodec(new MyObjectCodec()); // 若使用readValueAs系列方法,必须指定解码器
Person person = jsonParser.readValueAs(Person.class);
System.out.println(person);
// 关闭IO流
jsonParser.close();
}
运行程序,控制台完美输出:
Person(name=YourBatman, age=18, hobbies=[篮球, football], dog=Dog(name=旺财, color=WHITE))
注意:如果返回类型是List或者Map时,请不要使用readValueAs(Class<T> valueType)
方法,原因是集合类型泛型类型会被擦除从而无法自省,推荐使用readValueAs(TypeReference<?> valueTypeRef)
方法来处理
说明:专栏后面重点介绍的
ObjectMapper
,它就是ObjectCodec
的(唯一)实现类,它的基础原理便来源于此
总结
本文介绍了jackson-core
模块的流式API的使用,它作为JSON处理的基石,虽然极力不推荐直接使用,但这并不影响它的重要程度和地位。
对于流式API,虽然它在性能上有所特长,但是通过上面的Demo示例也可以知道:每一个 token都得自己增量处理(全手动档),换句话说,coder必须要非常小心地显示的处理每个token,这是很要命的,因为很可能会因为粗心导致丢掉/写错一些字符。而且这种方式书写的代码简洁性很差,可读性也不好,而且还得自己close流。因此,在不到需要考虑极致性能的时候,一定一定不要使用这种方式去操作JSON哦。
本文介绍它的目的并不是建议大家去使用,而是为了后面理解ObjectMapper夯实基础,毕竟做技术的要知其然且知其所以然了后才能坦然。
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |
- [享学Jackson] 一、初识Jackson – 世界上最好的JSON库
- [享学Jackson] 二、jackson-core之流式API与JsonFactory、JsonGenerator、JsonParser
- [享学Jackson] 三、jackson-databind之ObjectMapper与数据绑定、树模型
- [享学Jackson] 四、控制Jackson行为的特征们之JsonFactory.Feature、JsonGenerator.Feature、JsonParser.Feature
- [享学Jackson] 五、控制Jackson行为的特征们之JsonWriteFeature、JsonReadFeature
- [享学Jackson] 六、控制Jackson行为的特征们之MapperFeature、SerializationFeature、DeserializationFeature
- [享学Jackson] 七、Jackson使用bit位运算来开启/禁用Feature的原理解析
- [享学Jackson] 八、jackson-databind数据绑定基础配置之BaseSettings、MapperConfig、MapperConfigBase
- [享学Jackson] 九、jackson-databind数据绑定序列化/反序列化配置之SerializationConfig、DeserializationConfig
- [享学Jackson] 十、jackson-databind序列化之ObjectMapper序列化原理、序列化器匹配原理
- [享学Jackson] 十一、jackson-databind之JsonSerializer序列化器全解析
- [享学Jackson] 十二、jackson-databind反序列化之ObjectMapper反序列化原理、JsonDeserializer反序列化器全解析
- [享学Jackson] 十三、jackson-annotation注解模块全解析及Jackson注解大全
- [享学Jackson] 十四、深入理解Jackson的Module模块化设计及原理分析
- [享学Jackson] 十五、第三方模块Module的深度实践:JavaTimeModule、JSR310Module、ParameterNamesModule、Jdk8Module
- [享学Jackson] 十六、Jackson在Spring MVC中的使用之Date、JSR310时间类型的处理
- [享学Jackson] 十七、spring-web整合Jackson源码解析之Jackson2ObjectMapperBuilder
- [享学Jackson] 十八、Spring容器深度整合Jackson的桥梁之SpringHandlerInstantiator
- [享学Jackson] 十九、Spring下使用ObjectMapper的正确姿势 — Jackson2ObjectMapperFactoryBean
- [享学Jackson] 二十、Spring MVC下的Jackson — MappingJackson2HttpMessageConverter
- [享学Jackson] 二十一、Spring Boot下的Jackson — JacksonAutoConfiguration自动配置
- [享学Jackson] 二十二、Jackson与Fastjson的恩怨情仇(完结篇)