为了账号安全,请及时绑定邮箱和手机立即绑定

Jackson 2.18.0预览版概览

标签:
Java API

大约一个星期多前发布了 Jackson 2.18 的第一个候选版本——2.18.0-rc1。在等待官方的 2.18.0 版本发布(预计在一周或两周后,如果一切顺利)的同时,让我们来看看将包含的内容。(和往常一样,可以在 Jackson 2.18 发布 页面上查看详细信息)。

2.18 统计信息:

从 2.17.0 版本发布以来,开发大约历时了 5 个月,共有约 60 项改动(新功能和修复问题)横跨所有官方 Jackson 组件。
这可以再次被归类为一个“非常小的版本”——尽管从一个特定角度来看,它具有重要的里程碑意义:它完成了我视为进入 3.0.0 发行候选阶段前必不可少的一个关键功能。

必备功能:在 3.0 之前重写属性检查(introspection 内省)

在2.18要特别关注的一件事是实现databind#4515——“在Jackson 2.x中重写Bean属性内省逻辑”。这个计划已经持续了八年(2016年发布的Jackson 2.8发行说明中提到,尝试将其包含在2.9版本中)。
而现在我终于强迫自己在完成重写之前不开发任何其他特性,专心于这项任务。因此,在2024年5月,我花了大约两周的时间来弄清楚如何完成这项任务……然后,完成了它。呼,总算搞定了!
事后看来,我本应该早点做这件事:虽然这可能是这几年来我遇到的最具挑战性的任务,但五年之前做这件事并不比现在容易。不过:总比不做要好。

所以为什么要进行这件事呢?这次重写的主要动机是,有大约半打难以甚至无法修复的 Bug,这些 Bug 与属性内省逻辑中的几个问题相关。具体来说,就是“常规”属性(通过 getter、setter 或字段访问)和“创建者”属性(通过构造函数或工厂方法传递)的处理方式不统一。如果不统一这些处理方式,一个访问器中存在的注解将不会被其他访问器使用。这导致了在处理 Java 记录以及 Kotlin(和 Lombok)数据类时出现了一些难以解决的问题。

有时缺乏统一性也会阻碍“无注解”创建者对POJO的完全功能——现在应该可以顺利运行了:因此,如果你已经开启了“参数名称”模块,并且POJO只有一个 public构造函数,那么就无需任何注解,所以以下内容现在应该可以正常运行:

public class 我的类型 {  
  private int a, b; // a, b可以替换成有意义的中文变量名  
  public 我的类型(int a, int b) {  
    this.a = a;  
    this.b = b;  
  }  
  // 构造函数,初始化a和b的值
  public int getA() { return a; }  
  // 获取a的值
  public int getB() { return b; }  
  // 获取b的值
}

这次重写是我待办事项清单上必须要完成的一件特定事项,以便在2024年的剩余时间里为杰克逊3.0的预发布版本做准备——我想要2.x和3.x的代码库都采用“New Introspection”功能。

这对我来说很重要;希望这也将显著提升创作者(无论是显式的注释还是隐含的)的用户体验,这将消除一类以前无法解决的问题,即“为什么这里的注释不起作用呢?”。

最想要的功能:允许通过创建者使用@JsonAnySetter

Jackson-databind的9個“最想具備”的功能之一,即databind#562,在2.18版本中實現了。
這正是你所期待的:現在這個問題解決了。

    public class MyAnyType {  
      private final int id;  
      private final String name;  

      @JsonCreator  
      public MyAnyType(@JsonProperty("id") int id,  
        // 可以传入一个 'JsonNode',而不是 'Map'  
        @JsonAnySetter JsonNode extra) {  
        this.id = id;  
        name = extra.path("name").asText();  // 将 'name' 字段的值从 extra 中提取出来  
        // 等等  
      }  
    }

除了能够对 Map<String, ValueType>JsonNode 类型的值进行标注之外,它还能和 Java 的 Record 类型(以及 Kotlin 的数据类)一起使用(正如它应该的那样)。我们稍后会再详细讨论这一点。

表现:更快的浮点读取,更加

更快的浮点读取

我已经写过另一个重要的改进:“Jackson 2.18 浮点读取更快!”简单来说,从 2.17 升级到 2.18,读取 doubleBigDecimal 值的速度提升了 10–20%。性能提升的原因是消除了在常见解码路径上的 String 分配。无需更改任何代码,就能免费获得性能提升——不过,通常建议启用“快速浮点读取”功能,例如在 ‘Jackson 2.16 更快的 BigDecimal 读取’ 中有介绍:

    JsonFactory f = JsonFactory.builder()  
       .enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)  // 启用快速双精度解析器
       .enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)  // 启用快速大数解析器
       .build();  // 构建JsonFactory实例
    ObjectMapper mapper = new JsonMapper(f);  // 创建一个新的JsonMapper实例,并传入之前构建的JsonFactory
CSV:支持如向量值之类的修饰

这实际上是一个相当罕见的功能:我自己也需要的那种东西!我一直都在寻找包含大量向量的数据集——用于测试处理 JSON 中浮点值的性能(见上文),但似乎我没有收藏它(需要再找一次)。数据以 CSV 格式存在,而不是像这样的形式。

id,数值  
123,0.5,0.25,-0.1

Jackson的CSV模块可以处理数组值,数组值会用括号包围,如下所示:

    id, 向量值  
    A123,[0.5,0.25,-0.1]

感谢实现了dataformats-text#442,可以读取这些所谓的“装饰值”,以及实现了dataformats-text#495,可以写入这些值,这些(以及许多类似的情况)现在可以被妥善处理。读取可以像这样进行:

    @JsonPropertyOrder({"id", "vector" })  
    static class Embedding {  
        public String id;  
        public double[] vector;  
    }  

    // 可以手动构建 Schema,也可以像这里这样从类中自动检测:  
    CsvSchema schema = CsvMapper.schemaFor(Embedding.class)  
        .withHeader()  
        .withArrayElementSeparator(",")  
        // 可以使用一个默认的装饰器,还有简单的 '前缀/后缀' 装饰器,用于不同的开始/结束标记;严格的/可选的变体  
        .withColumn("vector",  
            col -> col.withValueDecorator(CsvValueDecorators.STRICT_BRACKETS_DECORATOR)  
        );  
    MappingIterator<Embedding> it = MAPPER.readerFor(Embedding.class)  
        .with(schema)  
        .readValues(new File("data.csv"));  

    // 可以读取所有数据,也可以像这里这样逐个处理  
    while (it.hasNextValue()) {  
      doSomething(it.nextValue());  
    }  
    // ... 就这样完成了!

写的时候也会用类似的 CsvSchema 来添加装饰。

请注意,此功能不仅限于数组值:它也可用于添加或移除(写入或读取)这些值的装饰。因此,它可能对处理 CSV 文件中的各种类型的值非常有用。

最后……我需要重新开始测试这种所谓的“稠密”向量数据的端到端性能提升。希望不久能在博客上分享我的进展。

对 Java Record 类型(及其类似)和 jackson-jr 的全面支持

上述提到的功能中,有两项改进了对Java Records(以及类似类型,如Kotlin数据类、Lombok生成的类)的处理。属性内省功能修复了注解使用中的问题;并且现在可以通过创建器使用@JsonAnySetter(由于Records是不可变的,无法使用字段或setter),这使得构建方式更加灵活。

除了改进 Jackson-databind 之外,小杰克森还通过 jackson-jr#162 添加了对 Java 记录的基本支持,这样记录值就可以像普通的 POJO 一样读取和写入。

Apache Avro 1.11 的 CVEs

长期以来,杰克逊 Avro 模块的一个版本问题是无法使用最新版本的 Apache Avro 库作为依赖项(该模块可以使用或不使用此库解码 Avro 内容,但在编码时会用到它):依赖项限制在 1.8.x 版本。
这主要是因为较旧版本的 Apache Avro 发布了 CVE。在dataformats-binary#167 之后,这些问题得到了解决,现在可以放心使用 1.11.x 版本。

一个额外的 StreamReadConstraints 选项 (maxTokenCount)

最近几次发布的版本中,我们为 jackson-core(即 JsonParserJsonGenerator)添加了一些“处理限制”设置:这些可配置的限制为防止潜在的 DoS 攻击提供保护。
Jackson 2.18 还新增了一个读取端的限制:maxTokenCount
它是在 jackson-core#1310 中添加的(详情请参见该链接)。

设置这样一个限制可以按照JSON Tokens的数量(而不是原始的byte长度,例如)来限制最大文档读取大小,还可以防止传递类似假的兆字节JSON。

    [[[[[[[[[],[],[],[]]]]]]]]

其中一个小文档可能会生成大量的标记(tokens),而且(如果使用databind)会占用比输入文档本身更多的内存。默认情况下,token的数量是无限制的;你可以将其设置为例如10,000个token,如下所示:

    final JsonFactory JSON_FACTORY = JsonFactory.builder()  
      .streamReadConstraints(StreamReadConstraints.builder().设置最大令牌数(10000).build())  
      .build();
下一步会是什么呢?

好的,下一步显然是发布 2.18.0 版本。到目前为止,2.18.0-rc1 版本中有一个回归问题被报告了,但现在这个问题已经被解决了,因此目前没有阻碍发布的障碍。不过我想等上一周左右,让 Jackson 的用户和开发者有更多时间进行测试。

但是除此之外——现在终于可以更专注于让Jackson 3.0.0准备好进入候选发布阶段了。分支本身已经准备了好几年,通过了测试套件等测试。但是还有一些决定要做,无论是项目和仓库结构方面——我们应该在现有仓库中使用单独的2.x和3.x分支,还是创建新的?——还是关于功能集。比如,是否还有大重构或重命名等待完成?目前,任何改变都是可能的:但是,一旦3.0.0发布,API将再次部分冻结(对于3.x系列)。因此,任何破坏性更改应在3.0.0发布前完成。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消