关于jackson:3-懂了这些方敢在简历上说会用Jackson写JSON

41次阅读

共计 10186 个字符,预计需要花费 26 分钟才能阅读完成。

你必须十分致力, 能力看起来毫不费力。本文已被 https://www.yourbatman.cn 收录,外面一并有 Spring 技术栈、MyBatis、JVM、中间件等小而美的 专栏 供以收费学习。关注公众号【BAT 的乌托邦】一一击破,深刻把握,回绝浅尝辄止。

前言

各位好,我是 A 哥(YourBatman)。上篇文章:2. 妈呀,Jackson 原来是这样写 JSON 的 晓得了 Jackson 写 JSON 的姿态,切实感触了一把 ObjectMapper 原来是这样实现序列化的 … 本文持续深刻探讨 JsonGenerator 写 JSON 的细节。

先闲聊几句题外话哈。咱们在书写简历的时候,都会用肯定篇幅展现本人的技能点(亮点),就像这样:

这一 part 十分重要,它决定了面试官是否有跟你聊的趣味,决定了你是否能在浩如烟海的简历中够怀才不遇。如何做到差异性?在当下如此发达的信息社会里,信息的获取唾手可得,所以在 常识的广度 方面,我认为人与人之间的差别其实并不大:

你晓得 DDD 畛域驱动、读过架构整洁之道、晓得六边形架构、晓得 DevOps…… 难道你还在想凭一些概念卖钱?拉出差距?
你在用 Spring 技术栈、在用 Redis、在用 ElasticSearch…… 难道你还认为当初像 10 年前一样,会用就能加分?

一聊就会,一问就退,一写就废。这是很多公司程序员的真实写照,基 / 中层管理者尤甚。早早的和技术渐行渐远,导致裁员潮到来时很容易取得一张“飞机票”,年纪越大,焦虑感越强。

在你的公司是否有过这种场景:四五个人指挥一个人干活 。对,就像这样:


扎不扎心,老铁????。不过不必乐观,从这应该你看到的是机会,习 xx 都说了实干能力兴邦嘛,2019 年裁员潮洗牌后,适者生存,不适者很多回老家了,这也让少量 很有实力 的程序员享受到了红利。应正了那句:当大潮褪去,才晓得谁在裸泳

扯远了,言归正传。Jackson 单会简略应用我认为还有余矣立足,那就跟我来吧~

版本约定

  • Jackson 版本:2.11.0
  • Spring Framework 版本:5.2.6.RELEASE
  • Spring Boot 版本:2.3.0.RELEASE

注释

一个框架 / 库好不好,不是看它的外围性能做得怎么样,而是非核心性能解决得如何。比方后盾页面做得咋样?容错机制呢?定制化、可配置化,扩展性等等。

Jackson 称得上优良(甚至最佳)最次要是得益于它优良的 module 模块化设计,在接触其之前,咱们先实现本章节的内容:JsonGenerator写 JSON 的行为管制(配置)。

配置属于程序的一部分,它影响着程序执行的方方面面。Spring应用 Environment/PropertySource 治理配置,对应的在 Jackson 里会看到有很多 Feature 类来管制 Jackson 的读 / 写行为,均是应用 enum 枚举类型来治理。

上篇文章 咱们学会了如何应用 JsonGenerator 去写一个 JSON,本文未来学习它的须要把握的应用细节。同样的,为围绕着 JsonGenerator 开展。

JsonGenerator 的 Feature

它是 JsonGenerator 的一个 外部 枚举类,共 10 个枚举值:

public enum Feature {

    // Low-level I/O
    AUTO_CLOSE_TARGET(true),
    AUTO_CLOSE_JSON_CONTENT(true),
    FLUSH_PASSED_TO_STREAM(true),

    // Quoting-related features
    @Deprecated
    QUOTE_FIELD_NAMES(true),
    @Deprecated
    QUOTE_NON_NUMERIC_NUMBERS(true),
    @Deprecated
    ESCAPE_NON_ASCII(false),
    @Deprecated
    WRITE_NUMBERS_AS_STRINGS(false),

    // Schema/Validity support features
    WRITE_BIGDECIMAL_AS_PLAIN(false),
    STRICT_DUPLICATE_DETECTION(false),
    IGNORE_UNKNOWN(false);
    
    ...
}

小贴士:枚举值均为 bool 类型,括号内为默认值

这个 Feature 的每个枚举值都管制着 JsonGenerator 写 JSON 时的不同行为,并且可分为三大类(源码处我也有标注):

  • Low-level I/O:底层 I / O 流相干。

Jackson 的流式 API 指的是 I / O 流,因而就波及到关流、flush 刷新流等操作

  • Quoting-related:双引号 ”” 援用相干。

JSON 标准规定 key 都必须有双引号,但这对于某些场景下并不需要

  • Schema/Validity support:束缚 / 标准 / 校验相干。

JSON 作为 K - V 构造的数据,那么容许雷同 key 呈现吗?这便由这些特色去管制

上面别离来意识意识它们。

AUTO_CLOSE_TARGET(true)

含意即为字面意:主动敞开指标(流)。

  • true:调用 JsonGenerator#close() 便会主动敞开底层的 I / O 流,你无需再关怀
  • false:底层 I / O 流请手动敞开

主动敞开:

@Test
public void test1() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// doSomething}
}

如果改为 false:那么你就须要本人 手动去 close底层应用的 OutputStream 或者 Writer。形如这样:

@Test
public void test2() throws IOException {JsonFactory factory = new JsonFactory();
    try (PrintStream err = System.err; JsonGenerator jg = factory.createGenerator(err, JsonEncoding.UTF8)) {
        // 特色置为 false 采纳手动关流的形式
        jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);

        // doSomething
    }
}

小贴士:例子均采纳 try-with-resources 形式关流,所以并没有显示调用 close()办法,你应该能懂吧????

AUTO_CLOSE_JSON_CONTENT(true)

先来看上面这段代码:

@Test
public void test3() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {jg.writeStartObject();
        jg.writeFieldName("names");

        // 写数组
        jg.writeStartArray();
        jg.writeString("A 哥");
        jg.writeString("YourBatman");
    }
}

运行程序,输入:

{"names":["A 哥","YourBatman"]}

wow,居然输入一切正常。仔细的你会发现,我的代码是缺胳膊少腿的:不论是 Object 还是 Array 都只 start 了,并没有显示调用 end 进行闭合。然而呢,后果却失常得很,这便是此 Feature 的作用了。

  • true:主动补齐(闭合)JsonToken#START_ARRAYJsonToken#START_OBJECT 类型的内容
  • false:啥都不做(不会被动抛错哦)

不过还是要啰嗦一句:尽管 Jackson 通过此 Feature 做了容错,然而本人在应用时,请务必 显示书写闭合

FLUSH_PASSED_TO_STREAM(true)

在应用带有缓冲区的 I / O 写数据时,短少“临门一脚”是初学者很容易犯的谬误,比方上面这个例子:

@Test
public void test4() throws IOException {JsonFactory factory = new JsonFactory();
    JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8);

    jg.writeStartObject();
    jg.writeStringField("name","A 哥");
    jg.writeEndObject();

    // jg.flush();
    // jg.close();}

运行程序,控制台没有任何输入。把正文代码放开任何一行,再次运行程序,控制台失常输入:

{"name":"A 哥"}
  • true:当 JsonGenerator 调用 close()/flush()办法时,主动强刷 I / O 流外面的数据
  • false:请手动解决

为何须要 flush()?

对于此问题这里小科普一下。因为向磁盘、网络写入数据的时候,出于效率的思考,操作系统 (话外音:这是操作系统为之)并不是输入一个字节就立即写入到文件或者发送到网络,而是把输入的字节先放到内存的一个缓冲区里(实质上就是一个 byte[] 数组),等到缓冲区写满了,再一次性写入文件或者网络。对于 很多 IO 设施 来说,一次写一个字节和一次写 1000 个字节,破费的工夫简直是齐全一样的,所以 OutputStream 有个 flush()办法,能 强制 把缓冲区内容输入。

小贴士:InputStream是没有 flush()办法的哦

通常状况下 ,咱们不须要调用这个 flush() 办法,因为缓冲区写满了,OutputStream 会主动调用它,并且,在调用 close()办法敞开 OutputStream 之前,也会主动调用 flush()办法强制刷一次缓冲区。然而,在某些状况下,咱们必须手动调用 flush()办法,比方上例子,比方发 IM 音讯 …

QUOTE_FIELD_NAMES(true)

此属性自 2.10 版本后已过期,应用 JsonWriteFeature#QUOTE_FIELD_NAMES 代替,利用在 JsonFactory 上,后文详解

JSON 对象字段名是否为应用 ”” 双引号括起来,这是 JSON 标准(RFC4627)规定的。

  • true:字段名应用 ”” 括起来 -> 遵循 JSON 标准
  • false:字段名 应用 ”” 括起来 -> 遵循 JSON 标准
@Test
public void test5() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// jg.disable(QUOTE_FIELD_NAMES);
        
        jg.writeStartObject();
        jg.writeStringField("name","A 哥");
        jg.writeEndObject();}
}

运行程序,输入:

{"name":"A 哥"}

99.99% 的状况下咱们不须要扭转默认值。Jackson 增加了 禁用引号 的性能以反对那十分不常见的状况,最常见的状况间接从 Javascript 中应用时可能会产生。

关上正文掉的语句,再次运行程序,输入:

{name:"A 哥"}

QUOTE_NON_NUMERIC_NUMBERS(true)

此属性自 2.10 版本后已过期,应用 JsonWriteFeature#WRITE_NAN_AS_STRINGS 代替,利用在 JsonFactory 上,后文详解

这个特色挺有意思,看例子(以写 Float 为例):

@Test
public void test6() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// jg.disable(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS);

        jg.writeNumber(0.9);
        jg.writeNumber(1.9);

        jg.writeNumber(Float.NaN);
        jg.writeNumber(Float.NEGATIVE_INFINITY);
        jg.writeNumber(Float.POSITIVE_INFINITY);
    }
}

运行程序,输入:

0.9 1.9 "NaN" "-Infinity" "Infinity"

同为 Float 数字类型,有的输入有 ”” 双引号包着,有的没有。放开正文的语句(禁用此特色),再次运行程序,输入:

0.9 1.9 NaN -Infinity Infinity

很显著,如果你是这么输入为一个 JSON 的话,那它就会是 非法的 JSON,是不合乎 JSON 规范的(因为像 NaN、Infinity 这种显著是字符串嘛,必须用 ”” 包起来才是非法的 value 值)。

因为 JSON 标准中对数字的严格定义,加上 Java 可能具备的 开放式数字集(如上例中 Float 类型并不 100% 是数字),很难做到既平安又不便,因而有了此特色让你依据须要来管制。

ESCAPE_NON_ASCII(false)

此属性自 2.10 版本后已过期,应用 JsonWriteFeature#ESCAPE_NON_ASCII 代替,利用在 JsonFactory 上,后文详解

@Test
public void test7() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// jg.enable(ESCAPE_NON_ASCII);
        jg.writeString("A 哥");
    }
}

运行程序,输入:

"A 哥"

放开注掉的代码(开启此属性),再次运行,输入:

"A\u54E5"

WRITE_NUMBERS_AS_STRINGS(false)

此属性自 2.10 版本后已过期,应用 JsonWriteFeature#WRITE_NUMBERS_AS_STRINGS 代替,利用在 JsonFactory 上,后文详解

该个性 强制 所有Java 数字写成字符串,即便底层数据格式真的是数字。

  • true:所有数字 强制 写为字符串
  • false:不做解决
@Test
public void test8() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// jg.enable(WRITE_NUMBERS_AS_STRINGS);

        Long num = Long.MAX_VALUE;
        jg.writeNumber(num);
    }
}

运行程序,输入:

9223372036854775807

放开正文代码(开启此特色),再次运行程序,输入:

"9223372036854775807"

有什么应用场景?一个用例是防止 Javascript 限度的问题:因为 Javascript 标准规定所有的数字解决都应该应用 64 位 ieee754 浮点值 来实现,后果是一些 64 位整数值不能被准确示意(因为尾数只有 51 位宽)。

采坑揭示:工夫戳后端用 Long 类型反给前端是没有问题的。但如果你是 很大的一个 Long 值(如雪花算法算出的很大的 Long 值),间接返回前端的话,Javascript 就会呈现精度失落的 bug

WRITE_BIGDECIMAL_AS_PLAIN(false)

管制写 java.math.BigDecimal 的行为:

  • true:应用 BigDecimal#toPlainString() 办法输入
  • false:应用默认输入形式(取决于 BigDecimal 是如何结构的)
@Test
public void test7() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// jg.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);

        BigDecimal bigDecimal1 = new BigDecimal(1.0);
        BigDecimal bigDecimal2 = new BigDecimal("1.0");
        BigDecimal bigDecimal3 = new BigDecimal("1E11");
        jg.writeNumber(bigDecimal1);
        jg.writeNumber(bigDecimal2);
        jg.writeNumber(bigDecimal3);
    }
}

运行程序,输入:

1 1.0 1E+11

放开正文代码,再次运行程序,输入:

1 1.0 100000000000

STRICT_DUPLICATE_DETECTION(false)

是否去严格的检测反复属性名。

  • true:检测是否有反复字段名,若有,则抛出 JsonParseException 异样
  • false:不检测 JSON 对象反复的字段名,即:雷同字段名都要解析
@Test
public void test8() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) {// jg.enable(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION);

        jg.writeStartObject();
        jg.writeStringField("name","YourBatman");
        jg.writeStringField("name","A 哥");
        jg.writeEndObject();}
}

运行程序,输入:

{"name":"YourBatman","name":"A 哥"}

关上正文掉的哪行代码:开启此特征值为 true。再次运行程序,输入:

com.fasterxml.jackson.core.JsonGenerationException: Duplicate field 'name'

    at com.fasterxml.jackson.core.json.JsonWriteContext._checkDup(JsonWriteContext.java:224)
    at com.fasterxml.jackson.core.json.JsonWriteContext.writeFieldName(JsonWriteContext.java:217)
    ...

留神:审慎关上此开关,如果查看的话性能会降落 20%-30%。

IGNORE_UNKNOWN(false)

如果 底层数据格式 须要输入所有属性,以及如果 找不到 调用者试图写入的属性的定义,则该个性确定是否要执行的操作。

可能你听完还一脸懵逼,什么底层数据格式,什么找不到,我明明是写 JSON 啊,何解?其实这不是针对于写 JSON 来说的,对于 JSON,这个个性没有成果,因为属性不须要事后定义。通常,大多数文本数据格式不须要模式信息,而某些二进制数据格式须要定义(如 Avro、protobuf),因而这个属性是为它们而生(Smile、BSON 等这些二进制也是不须要预约模式信息的哦)。

强调:JsonGenerator不是只能写 JSON 格局,毕竟底层是 I / O 流嘛,实践上啥都能写

  • true:启动该性能

能够事后调用(在写数据之前)这个 API 设定好模式信息即可:

JsonGenerator:public void setSchema(FormatSchema schema) {...}
  • false:禁用该性能。如果底层数据格式须要所有属性的常识能力输入,那就抛出 JsonProcessingException 异样

定制 Feature

通过上一 part 通晓了管制 JsonGenerator 的特征值们,以及其作用是。Feature 的每个枚举值都有个默认值(括号外面),那么如果咱们心愿对 不同的 JsonGenerator 实例 利用不同的配置该怎么办呢?

自然而然的 JsonGenerator 提供了相干 API 供以咱们操作:

// 开启
public abstract JsonGenerator enable(Feature f);
// 敞开
public abstract JsonGenerator disable(Feature f);
// 开启 / 敞开
public final JsonGenerator configure(Feature f, boolean state) {...};

public abstract boolean isEnabled(Feature f);
public boolean isEnabled(StreamWriteFeature f) {...};

替换者:StreamWriteFeature

本类是 2.10 版本新增的,用于齐全替换下面的 Feature。目标:齐全独立的属性配置,不依赖于任何后端格局,因为 JsonGenerator 并不局限于写 JSON,因而把 Feature 放在 JsonGenerator 作为外部类是不太适合的,所以独自摘出来。

StreamWriteFeature 用在 JsonFactory 里,前面再解说到它的构建器 JsonFactoryBuilder 时再具体探讨。

序列化 POJO 对象

上篇文章用代码演示过了如何应用 writeObject(Object pojo) 来把一个 POJO 一次性序列化成为一个 JSON 串,它次要依赖于 ObjectCodec 去实现:

public abstract JsonGenerator setCodec(ObjectCodec oc);

ObjectCodec 堪称是 Jackson 里极其重要的一个根底组件,咱们最相熟的 ObjectMapper 它就是一个解码器,实现了序列化和反序列化、树模型等操作。这将在前面章节里重点介绍~

输入丑陋的 JSON 格局

咱们晓得 JSON 之所以疾速风行的起因之一是得益于它的 可读性好,可读性好又体现在它丑陋的(规定)的展现格局上。

默认状况下,应用 JsonGenerator 写 JSON 时,所有的局部都是输入在同一行里,显然这种格局对人浏览来说是不够敌对的。作为最风行的 JSON 库天然思考到了这一点,提供了格式化器来 丑化输入

// 本人指定丑陋格局打印器
public JsonGenerator setPrettyPrinter(PrettyPrinter pp) {...}

// 利用默认的丑陋格局打印器
public abstract JsonGenerator useDefaultPrettyPrinter();

PrettyPrinter 有如下两个实现类:

应用不同的实现类,对输入后果的影响如下:

什么都不设置:MinimalPrettyPrinter:{"zhName":"A 哥","enName":"YourBatman","age":18}

DefaultPrettyPrinter:useDefaultPrettyPrinter():
{
  "zhName" : "A 哥",
  "enName" : "YourBatman",
  "age" : 18
}

由此可见,在什么都不设置的状况下,后果会全副在一行显示(紧凑型输入)。DefaultPrettyPrinter示意带层级格局的输入(可读性好),若有此须要,倡议间接调用更为快捷的 useDefaultPrettyPrinter() 办法,而不必本人去 new 一个实例。

总结

本文的次要内容和重点是介绍了用 Feature 去管制 JsonGenerator 的写行为,不同的特征值管制着不同的行为。在理论应用时可针对不同的需要,定制出不同的 JsonGenerator 实例,就地取材和相互隔离。

相干举荐:
  • Fastjson 到了说再见的时候了
  • 1. 初识 Jackson — 世界上最好的 JSON 库
  • 2. 妈呀,Jackson 原来是这样写 JSON 的

关注 A 哥

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

正文完
 0