公司不是你家,领导不是你妈。本文已被 https://www.yourbatman.cn 收录,外面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以收费学习。关注公众号【BAT的乌托邦】一一击破,深刻把握,回绝浅尝辄止。

前言

各位好,我是A哥(YourBatman)。上篇文章:3. 懂了这些,方敢在简历上说会用Jackson写JSON 聊完,流式API的写局部能够认为你已齐全把握了,本文理解它读的局部。

版本约定

  • Jackson版本:2.11.0
  • Spring Framework版本:5.2.6.RELEASE
  • Spring Boot版本:2.3.0.RELEASE
小贴士:截止到本文,本系列后面所有示例都只仅仅导入jackson-core而已,后续若要新增jar包我会额定阐明,否则雷同

注释

什么叫读JSON?就是把一个JSON 字符串 解析为对象or树模型嘛,因而也称作解析JSON串。Jackson底层流式API应用JsonParser来实现JSON字符串的解析。

最简应用Demo

筹备一个POJO:

@Datapublic class Person {    private String name;    private Integer age;}

测试用例:把一个JSON字符串绑定(封装)进一个POJO对象里

@Testpublic void test1() throws IOException {    String jsonStr = "{\"name\":\"YourBatman\",\"age\":18}";    Person person = new Person();    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {                // 只有还没完结"}",就始终读        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("name".equals(fieldname)) {                jsonParser.nextToken();                person.setName(jsonParser.getText());            } else if ("age".equals(fieldname)) {                jsonParser.nextToken();                person.setAge(jsonParser.getIntValue());            }        }                System.out.println(person);    }}

运行程序,输入:

Person(name=YourBatman, age=18)

胜利把一个JSON字符串的值解析到Person对象。你可能会疑难,怎么这么麻烦?那当然,这是底层流式API,纯手动档嘛。你取得了性能,可不要失去一些便捷性嘛。

小贴士:底层流式API个别面向“专业人士”,利用级开发应用高阶API ObjectMapper即可。当然,读完本系列就能让你齐全具备“专业人士”的实力????

JsonParser针对不同的value类型,提供了十分多的办法用于理论值的获取。

间接值获取:

// 获取字符串类型public abstract String getText() throws IOException;// 数字Number类型值 标量值(反对的Number类型参照NumberType枚举)public abstract Number getNumberValue() throws IOException;public enum NumberType {    INT, LONG, BIG_INTEGER, FLOAT, DOUBLE, BIG_DECIMAL};public abstract int getIntValue() throws IOException;public abstract long getLongValue() throws IOException;...public abstract byte[] getBinaryValue(Base64Variant bv) throws IOException;

这类办法可能会抛出异样:比方value值本不是数字但你调用了getInValue()办法~

小贴士:如果value值是null,像getIntValue()、getBooleanValue()等这种间接获取办法是会抛出异样的,但getText()不会

带默认值的值获取,具备更好安全性:

public String getValueAsString() throws IOException {    return getValueAsString(null);}public abstract String getValueAsString(String def) throws IOException;...public long getValueAsLong() throws IOException {    return getValueAsLong(0);}public abstract long getValueAsLong(long def) throws IOException;...

此类办法若碰到数据的转换失败时,不会抛出异样,把def作为默认值返回。

组合办法

JsonGenerator一样,JsonParser也提供了高钙片组合办法,让你更加便捷的应用。

主动绑定

听起来像高级性能,是的,它必须依赖于ObjectCodec去实现,因为理论是全副委托给了它去实现的,也就是咱们最为相熟的readXXX系列办法:

咱们晓得,ObjectMapper就是一个ObjectCodec,它属于高级API,本文显然不会用到ObjectMapper它喽,因而咱们本人手敲一个实现来实现此性能。

自定义一个ObjectCodec,Person类专用:用于把JSON串主动绑定到实例属性。

public class PersonObjectCodec extends ObjectCodec {    ...    @SneakyThrows    @Override    public <T> T readValue(JsonParser jsonParser, Class<T> valueType) throws IOException {        Person person = (Person) valueType.newInstance();        // 只有还没完结"}",就始终读        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("name".equals(fieldname)) {                jsonParser.nextToken();                person.setName(jsonParser.getText());            } else if ("age".equals(fieldname)) {                jsonParser.nextToken();                person.setAge(jsonParser.getIntValue());            }        }        return (T) person;    }    ...}

有了它,就能够实现咱们的主动绑定了,书写测试用例:

@Testpublic void test3() throws IOException {    String jsonStr = "{\"name\":\"YourBatman\",\"age\":18, \"pickName\":null}";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        jsonParser.setCodec(new PersonObjectCodec());        System.out.println(jsonParser.readValueAs(Person.class));    }}

运行程序,输入:

Person(name=YourBatman, age=18)

这就是ObjectMapper主动绑定的外围原理所在,其它更为弱小能力将在后续章节具体开展。

JsonToken

在上例解析过程中,有一个十分重要的角色,那便是:JsonToken。它示意解析JSON内容时,用于返回后果的根本标记类型的枚举。

public enum JsonToken {    NOT_AVAILABLE(null, JsonTokenId.ID_NOT_AVAILABLE),        START_OBJECT("{", JsonTokenId.ID_START_OBJECT),    END_OBJECT("}", JsonTokenId.ID_END_OBJECT),    START_ARRAY("[", JsonTokenId.ID_START_ARRAY),    END_ARRAY("]", JsonTokenId.ID_END_ARRAY),    // 属性名(key)    FIELD_NAME(null, JsonTokenId.ID_FIELD_NAME),    // 值(value)    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),}

为了辅助了解,A哥用一个例子,输入各个局部高深莫测:

@Testpublic void test2() throws IOException {    String jsonStr = "{\"name\":\"YourBatman\",\"age\":18, \"pickName\":null}";    System.out.println(jsonStr);    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        while (true) {            JsonToken token = jsonParser.nextToken();            System.out.println(token + " -> 值为:" + jsonParser.getValueAsString());            if (token == JsonToken.END_OBJECT) {                break;            }        }    }}

运行程序,输入:

{"name":"YourBatman","age":18, "pickName":null}START_OBJECT -> 值为:nullFIELD_NAME -> 值为:nameVALUE_STRING -> 值为:YourBatmanFIELD_NAME -> 值为:ageVALUE_NUMBER_INT -> 值为:18FIELD_NAME -> 值为:pickNameVALUE_NULL -> 值为:nullEND_OBJECT -> 值为:null

从左至右解析,一一对应。各个局部用上面这张图能够简略示意进去:

小贴士:解析时请确保你的的JSON串是非法的,否则抛出JsonParseException异样

JsonParser的Feature

它是JsonParser的一个外部枚举类,共15个枚举值:

public enum Feature {    AUTO_CLOSE_SOURCE(true),        ALLOW_COMMENTS(false),    ALLOW_YAML_COMMENTS(false),    ALLOW_UNQUOTED_FIELD_NAMES(false),    ALLOW_SINGLE_QUOTES(false),    @Deprecated    ALLOW_UNQUOTED_CONTROL_CHARS(false),    @Deprecated    ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER(false),    @Deprecated    ALLOW_NUMERIC_LEADING_ZEROS(false),    @Deprecated    ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS(false),    @Deprecated    ALLOW_NON_NUMERIC_NUMBERS(false),    @Deprecated    ALLOW_MISSING_VALUES(false),    @Deprecated    ALLOW_TRAILING_COMMA(false),        STRICT_DUPLICATE_DETECTION(false),    IGNORE_UNDEFINED(false),    INCLUDE_SOURCE_IN_LOCATION(true);}
小贴士:枚举值均为bool类型,括号内为默认值

每个枚举值都管制着JsonParser不同的行为。上面分类进行解释

底层I/O流相干

自2.10版本后,应用StreamReadFeature#AUTO_CLOSE_SOURCE代替

Jackson的流式API指的是I/O流,所以即便是,底层也是用I/O流(Reader)去读取而后解析的。

AUTO_CLOSE_SOURCE(true)

原理和JsonGenerator的AUTO_CLOSE_TARGET(true)一样,不再解释,详见上篇文章对应局部。

反对非标准格局

JSON是有标准的,在它的标准里并没有形容到对正文的规定、对控制字符的解决等等,也就是说这些均属于非标准行为。比方这个JSON串:

{    "name" : "YourBarman", // 名字    "age" : 18 // 年龄}

你看,若你这么写IDEA都会飘红提醒你:

然而,在很多应用场景(特地是JavaScript)里,咱们会在JSON串里写正文(属性多时尤甚)那么对于这种串,JsonParser如何管制解决呢?它提供了对非标准JSON格局的兼容,通过上面这些特征值来管制。

ALLOW_COMMENTS(false)

自2.10版本后,应用JsonReadFeature#ALLOW_JAVA_COMMENTS代替

是否容许/* */或者//这种类型的正文呈现。

@Testpublic void test4() throws IOException {    String jsonStr = "{\n" +            "\t\"name\" : \"YourBarman\", // 名字\n" +            "\t\"age\" : 18 // 年龄\n" +            "}";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        // 开启正文反对        // jsonParser.enable(JsonParser.Feature.ALLOW_COMMENTS);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("name".equals(fieldname)) {                jsonParser.nextToken();                System.out.println(jsonParser.getText());            } else if ("age".equals(fieldname)) {                jsonParser.nextToken();                System.out.println(jsonParser.getIntValue());            }        }    }}

运行程序,抛出异样:

com.fasterxml.jackson.core.JsonParseException: Unexpected character ('/' (code 47)): maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser) at [Source: (String)"{    "name" : "YourBarman", // 名字    "age" : 18 // 年龄}"; line: 2, column: 26]

放开正文的代码,再次运行程序,失常work

ALLOW_YAML_COMMENTS(false)

自2.10版本后,应用JsonReadFeature#ALLOW_YAML_COMMENTS代替

顾名思义,开启后将反对Yaml格局的的正文,也就是#模式的正文语法。

ALLOW_UNQUOTED_FIELD_NAMES(false)

自2.10版本后,应用JsonReadFeature#ALLOW_UNQUOTED_FIELD_NAMES代替

是否容许属性名不带双引号"",比较简单,示例略。

ALLOW_SINGLE_QUOTES(false)

自2.10版本后,应用JsonReadFeature#ALLOW_SINGLE_QUOTES代替

是否容许属性名反对单引号,也就是应用''包裹,形如这样:

{    'age' : 18}

ALLOW_UNQUOTED_CONTROL_CHARS(false)

自2.10版本后,应用JsonReadFeature#ALLOW_UNESCAPED_CONTROL_CHARS代替

是否容许JSON字符串蕴含非引号控制字符(值小于32的ASCII字符,蕴含制表符和换行符)。 因为JSON标准要求对所有控制字符应用引号,这是一个非标准的个性,因而默认禁用。

那么,哪些字符属于控制字符呢?做个简略科普:咱们个别说的ASCII码共128个字符(7bit),共分为两大类

控制字符

控制字符,也叫不可打印字符。第0~32号落第127号(共34个)是控制字符,例如常见的:LF(换行)CR(回车)、FF(换页)、DEL(删除)、BS(退格)等都属于此类。

控制字符大部分曾经废除不必了,它们的用处次要是用来操控曾经解决过的文字,ASCII值为8、9、10 和13 别离转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。

话外音:你看不见我,但我对你影响还蛮大
非控制字符

也叫可显示字符,或者可打印字符,能从键盘间接输出的字符。比方0-9数字,逗号、分号这些等等。

话外音:你肉眼能看到的字符就属于非控制字符

ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER(false)

自2.10版本后,应用JsonReadFeature#ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER代替

是否容许*反斜杠*本义任何字符。这句话不是十分好了解,看上面这个例子:

@Testpublic void test4() throws IOException {    String jsonStr = "{\"name\" : \"YourB\\'atman\" }";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        // jsonParser.enable(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("name".equals(fieldname)) {                jsonParser.nextToken();                System.out.println(jsonParser.getText());            }        }    }}

运行程序,报错:

com.fasterxml.jackson.core.JsonParseException: Unrecognized character escape ''' (code 39) at [Source: (String)"{"name" : "YourB\'atman" }"; line: 1, column: 19] ...

放开正文掉的代码,再次运行程序,一切正常,输入:YourB'atman

ALLOW_NUMERIC_LEADING_ZEROS(false)

自2.10版本后,应用JsonReadFeature#ALLOW_LEADING_ZEROS_FOR_NUMBERS代替

是否容许像00001这样的“数字”呈现(而不报错)。看例子:

@Testpublic void test5() throws IOException {    String jsonStr = "{\"age\" : 00018 }";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        // jsonParser.enable(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("age".equals(fieldname)) {                jsonParser.nextToken();                System.out.println(jsonParser.getIntValue());            }        }    }}

运行程序,输入:

com.fasterxml.jackson.core.JsonParseException: Invalid numeric value: Leading zeroes not allowed at [Source: (String)"{"age" : 00018 }"; line: 1, column: 11] ...

放开注掉的代码,再次运行程序,一切正常。输入18

ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS(false)

自2.10版本后,应用JsonReadFeature#ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS代替

是否容许小数点.打头,也就是说.1这种小数格局是否非法。默认是不非法的,须要开启此特色能力反对,例子就略了,根本同上。

ALLOW_NON_NUMERIC_NUMBERS(false)

自2.10版本后,应用JsonReadFeature#ALLOW_NON_NUMERIC_NUMBERS代替

是否容许一些解析器辨认一组“非数字”(如NaN)作为非法的浮点数值。这个属性和上篇文章的JsonGenerator#QUOTE_NON_NUMERIC_NUMBERS特征值是一唱一和的。

@Testpublic void test5() throws IOException {    String jsonStr = "{\"percent\" : NaN }";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        // jsonParser.enable(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("percent".equals(fieldname)) {                jsonParser.nextToken();                System.out.println(jsonParser.getFloatValue());            }        }    }}

运行程序,抛错:

com.fasterxml.jackson.core.JsonParseException: Non-standard token 'NaN': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow at [Source: (String)"{"percent" : NaN }"; line: 1, column: 17]

放开正文掉的代码,再次运行,一切正常。输入:

NaN
小贴士:NaN也能够示意一个Float对象,是的你没听错,即便它不是数字但它也是Float类型。具体你能够看看Float源码里的那几个常量

ALLOW_MISSING_VALUES(false)

自2.10版本后,应用JsonReadFeature#ALLOW_MISSING_VALUES代替

是否容许反对JSON数组中“缺失”值。怎么了解:数组中缺失了值示意两个逗号之间,啥都没有,形如这样[value1, , value3]

@Testpublic void test6() throws IOException {    String jsonStr = "{\"names\" : [\"YourBatman\",,\"A哥\",,] }";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        // jsonParser.enable(JsonParser.Feature.ALLOW_MISSING_VALUES);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("names".equals(fieldname)) {                jsonParser.nextToken();                while (jsonParser.nextToken() != JsonToken.END_ARRAY) {                    System.out.println(jsonParser.getText());                }            }        }    }}

运行程序,抛错:

YourBatman // 能输入一个,毕竟第一个part(JsonToken)是失常的嘛com.fasterxml.jackson.core.JsonParseException: Unexpected character (',' (code 44)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at [Source: (String)"{"names" : ["YourBatman",,"A哥",,] }"; line: 1, column: 27]

放开正文掉的代码,再次运行,一切正常,后果为:

YourBatmannullA哥nullnull

请留神:此时数组的长度是5哦。

小贴士:此处用的String类型展现后果,是因为null能够作为String类型(jsonParser.getText()失去null是非法的)。但如果你应用的int类型(或者bool类型),那么如果是null的话就报错喽Current token (VALUE_NULL) not of boolean type,有趣味的亲可自行尝试,坚固下了解的成果。报错起因文上已有阐明~

ALLOW_TRAILING_COMMA(false)

自2.10版本后,应用JsonReadFeature#ALLOW_TRAILING_COMMA代替

是否容许最初一个多余的逗号(肯定是最初一个)。这个特色是十分重要的,若开关关上,有如下成果:

  • [true,true,]等价于[true, true]
  • {"a": true,}等价于{"a": true}

当这个特色和下面的ALLOW_MISSING_VALUES特色同时应用时,本特色优先级更高。也就是说:会先去除掉最初一个逗号后,再进行数组长度的计算。

举个例子:当然这两个特色开关都关上时,[true,true,]等价于[true, true]好了解;并且呢,[true,true,,]是等价于[true, true, null]的哦,可千万别疏忽最初的这个null

@Testpublic void test7() throws IOException {    String jsonStr = "{\"results\" : [true,true,,] }";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        jsonParser.enable(JsonParser.Feature.ALLOW_MISSING_VALUES);        // jsonParser.enable(JsonParser.Feature.ALLOW_TRAILING_COMMA);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("results".equals(fieldname)) {                jsonParser.nextToken();                while (jsonParser.nextToken() != JsonToken.END_ARRAY) {                    System.out.println(jsonParser.getBooleanValue());                }            }        }    }}

运行程序,输入:

YourBatmannullA哥nullnull

这齐全就是上例的成果嘛。当初我放开正文掉的代码,再次运行,后果为:

YourBatmannullA哥null

请留神比照前后的后果差别,并本人能能本人正当解释

校验相干

Jackson在JSON规范之外,给出了两个校验相干的特色。

STRICT_DUPLICATE_DETECTION(false)

自2.10版本后,应用StreamReadFeature#STRICT_DUPLICATE_DETECTION代替

是否容许JSON串有两个雷同的属性key,默认是容许的

@Testpublic void test8() throws IOException {    String jsonStr = "{\"age\":18, \"age\": 28 }";    JsonFactory factory = new JsonFactory();    try (JsonParser jsonParser = factory.createParser(jsonStr)) {        // jsonParser.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {            String fieldname = jsonParser.getCurrentName();            if ("age".equals(fieldname)) {                jsonParser.nextToken();                System.out.println(jsonParser.getIntValue());            }        }    }}

运行程序,失常输入:

1828

若放开正文代码,再次运行,则抛错:

18 // 第一个数字还是能失常输入的哟com.fasterxml.jackson.core.JsonParseException: Duplicate field 'age' at [Source: (String)"{"age":18, "age": 28 }"; line: 1, column: 17]

IGNORE_UNDEFINED(false)

自2.10版本后,应用StreamReadFeature#IGNORE_UNDEFINED代替

是否疏忽没有定义的属性key。和JsonGenerator.Feature#IGNORE_UNKNOWN的这个特色一样,它作用于事后定义了格局的数据类型,如Avro、protobuf等等,JSON是不须要事后定义的哦~

同样的,你能够通过这个API事后设置格局:

JsonParser:    public void setSchema(FormatSchema schema) {        ...    }

其它

INCLUDE_SOURCE_IN_LOCATION(true)

自2.10版本后,应用StreamReadFeature#INCLUDE_SOURCE_IN_LOCATION代替

是否构建JsonLocation对象来示意每个part的起源,你能够通过JsonParser#getCurrentLocation()来拜访。作用不大,就此略过。

总结

本文介绍了底层流式API JsonParser读JSON的形式,它不仅仅可能解决规范JSON,也能通过Feature特征值来管制,开启对一些非标准但又比拟罕用的JSON串的反对,这不正式一个优良框架/库应有的态度麽:兼容性

联合上篇文章对写JSON时JsonGenerator的形容,可能总结出两点准则:

  • 写:100%遵循标准
  • 读:最大水平兼容并包

写代表你的输入,遵循标准的输入能确保第三方在用你输入的数据时不至于对你破口大骂,所以这是你应该做好的本分。读代表你的输出,可能解决标准的格局是你的职责,但我若还能额定的解决一些非标准格局(个别为罕用的),那相对是闪耀点,也就是你给的情分。本分是你应该做的,而情分就是你的加分项。

相干举荐:
  • Fastjson到了说再见的时候了
  • 1. 初识Jackson -- 世界上最好的JSON库
  • 2. 妈呀,Jackson原来是这样写JSON的
  • 3. 懂了这些,方敢在简历上说会用Jackson写JSON

关注A哥

AuthorA哥(YourBatman)
集体站点www.yourbatman.cn
E-mailyourbatman@qq.com
微 信fsx641385712
沉闷平台
公众号BAT的乌托邦(ID:BAT-utopia)
常识星球BAT的乌托邦
每日文章举荐每日文章举荐