概述
ACAL Lynxus 提供了一组非常强大的处理器,能够摄取、处理、路由、转换和传递任何格式的数据。这之所以可能,是因为 Lynxus 框架本身是数据无关的。它不关心您的数据是 100 字节的 JSON 消息还是 100 GB 的视频。这是一个非常强大的功能。然而,已经有许多模式迅速发展起来,用于处理不同类型的数据。
Lynxus 经常处理的一类数据是面向记录的数据。当我们说面向记录的数据时,我们通常(但不总是)在谈论结构化数据,如 JSON、CSV 和 Avro。不过,还有许多其他类型的数据也可以表示为"记录"或"消息"。因此,已经开发了一组控制器服务来解析这些不同的数据格式,并通过使用 RecordReader API 以一致的方式表示数据。这允许以相同的方式处理以任何数据格式编写的数据,只要有能够生成表示数据的 Record 对象的 RecordReader 即可。
当我们谈论 Record 时,这是一个抽象,允许我们以相同的方式处理数据,无论其格式如何。Record 由一个或多个 Field 组成。每个 Field 都有一个关联的名称和 Type。Record 的 Field 使用 Record Schema 来描述。Schema 指示哪些 Field 组成特定类型的 Record。Field 的 Type 将是以下之一:
-
String
-
Boolean
-
Byte
-
Character
-
Short
-
Integer
-
Long
-
BigInt
-
Float
-
Double
-
Date - 表示不带时间组件的日期
-
Time - 表示不带日期组件的时间
-
Timestamp - 表示日期和时间
-
Embedded Record - 分层数据(如 JSON)可以通过允许字段本身为 Record 类型来表示。
-
Choice - 字段可以是多种类型之一。
-
Array - 数组的所有元素具有相同类型。
-
Map - 所有 Map 键都是 String 类型。值具有相同类型。
一旦数据流被转换为 Record,RecordWriter API 允许我们将这些 Record 序列化回字节流,以便可以传递给其他系统。
当然,如果我们不打算在读写之间对数据做些什么,那么读写这些数据就没有多大意义。Lynxus 已经开发了几个处理器,为路由、查询和转换面向记录的数据提供了一些非常强大的功能。通常,为了执行所需的功能,处理器需要用户的输入来确定 Record 中的哪些字段或 Record 中的哪些值应该被操作。
这就是 Lynxus RecordPath 语言的用武之地。RecordPath 旨在成为一个简单、易用的领域特定语言 (DSL),用于指定在配置处理器时我们关心或想要访问的 Record 中的哪些字段。
RecordPath 的结构
Lynxus 中的 Record 由(可能)许多字段组成,这些字段中的每一个实际上本身也可以是一个 Record。这意味着 Record 可以被认为具有分层或嵌套的结构。我们将"内部 Record"称为"外部 Record"的子级。那么,内部 Record 的子级就是最外层 Record 的后代。类似地,我们可以将外部 Record 称为内部 Record 的祖先。
子级运算符
RecordPath 语言的结构使我们能够轻松引用最外层 Record 的字段、子 Record 的字段或后代 Record 的字段。为此,我们用斜杠字符(/)分隔子级的名称,我们称之为 child 运算符。例如,假设我们有一个由两个字段组成的 Record:name 和 details。还假设 details 是一个本身就是 Record 的字段,并有两个字段:identifier 和 address。进一步,假设 address 本身是一个包含 5 个字段的 Record:number、street、city、state 和 zip。一个以 JSON 编写的示例(用于说明目的)可能如下所示:
{
"name": "John Doe",
"details": {
"identifier": 100,
"address": {
"number": "123",
"street": "5th Avenue",
"city": "New York",
"state": "NY",
"zip": "10020"
}
}
}
我们可以通过使用 RecordPath /details/address/zip 来引用 zip 字段。这告诉我们想要使用"根"Record 的 details 字段。然后我们想要引用子 Record 的 address 字段和该 Record 的 zip 字段。
后代运算符
除了提供到达 zip 字段的显式路径外,有时在不知道完整路径的情况下引用 zip 字段可能很有用。在这种情况下,我们可以使用 descendant 运算符(//)代替 child 运算符(/)。要到达与上面相同的 zip 字段,我们可以简单地使用路径 //zip 来实现。
但是,child 运算符和 descendant 运算符之间有一个非常重要的区别:descendant 运算符可能匹配多个字段,而 child 运算符最多匹配一个字段。为了帮助理解这一点,考虑以下 Record:
{
"name": "John Doe",
"workAddress": {
"number": "123",
"street": "5th Avenue",
"city": "New York",
"state": "NY",
"zip": "10020"
},
"homeAddress": {
"number": "456",
"street": "116th Avenue",
"city": "New York",
"state": "NY",
"zip": "11697"
}
}
现在,如果我们使用 RecordPath /workAddress/zip,我们将引用值为 "10020" 的 zip 字段。RecordPath /homeAddress/zip 将引用值为 "11697" 的 zip 字段。但是,RecordPath //zip 将引用这两个字段。
过滤器
通过上述示例和说明,我们能够轻松引用 Record 中的特定字段。然而,在实际场景中,数据很少像上述示例那样简单。通常,我们需要过滤或细化我们引用的字段。我们可能需要这样做的例子包括:当我们引用一个 Array 字段并且只想引用数组中的某些元素时;当我们引用一个 Map 字段并想引用 Map 中的一个或几个特定条目时;或者当我们想仅在 Record 符合某些条件时引用它时。我们可以通过在方括号内(使用 [ 和 ] 字符)向 RecordPath 提供条件来实现这一点。我们将在下面逐一讨论这些情况。
函数用法
除了从 Record 中检索字段(如上面 过滤器 部分所述),有时我们需要细化想要选择的字段。或者我们可能想返回一个字段的修改版本。为此,我们依赖函数。函数的语法为 <函数名> <左括号> <参数> <右括号>,其中 <参数> 表示一个或多个用逗号分隔的参数。参数可以是字符串字面量(如 'hello')或数字字面量(如 48),也可以是相对或绝对的 RecordPath(如 ./name 或 /id)。此外,我们可以在过滤器中使用函数。例如,我们可以使用 RecordPath /person[ isEmpty('name') ]/id 来检索名称为空的任何人的 id 字段。可用函数及其相应文档的列表可以在下面的 函数 部分找到。
数组
当我们引用一个 Array 字段时,该字段的值可能是包含多个元素的数组,但我们可能只需要其中几个元素。例如,我们可能只想引用第一个元素;只有最后一个元素;或者可能是第一个、第二个、第三个和最后一个元素。我们可以通过使用方括号内元素的索引(索引从 0 开始)来引用特定元素。因此,让我们考虑上述 Record 的修改版本:
{
"name": "John Doe",
"addresses": [
"work": {
"number": "123",
"street": "5th Avenue",
"city": "New York",
"state": "NY",
"zip": "10020"
},
"home": {
"number": "456",
"street": "116th Avenue",
"city": "New York",
"state": "NY",
"zip": "11697"
}
]
}
我们现在可以使用 RecordPath /addresses[0] 引用 addresses 数组中的第一个元素。我们可以使用 RecordPath /addresses[1] 访问第二个元素。但是,有时我们不知道数组中会有多少元素。因此我们可以使用负索引从数组末尾向前计数。例如,我们可以使用 /addresses[-1] 访问最后一个元素,或使用 /addresses[-2] 访问倒数第二个元素。如果我们想引用多个元素,可以使用逗号分隔的元素列表,如 /addresses[0, 1, 2, 3]。或者,要访问第 0 到第 8 个元素,我们可以使用 range 运算符(..),如 /addresses[0..8]。我们也可以混合使用这些方式,并使用语法 /addresses[0..-1] 引用所有元素,甚至 /addresses[0, 1, 4, 6..-1]。当然,并非此处引用的所有索引都会匹配上述 Record,因为 addresses 数组只有 2 个元素。不匹配的索引将被简单地跳过。
Map
与 Array 字段类似,Map 字段可能由多个不同的值组成。RecordPath 为我们提供了根据其键选择一组值的能力。我们通过在方括号内使用带引号的字符串来实现这一点。作为示例,让我们重新审视上面的原始 Record:
{
"name": "John Doe",
"details": {
"identifier": 100,
"address": {
"number": "123",
"street": "5th Avenue",
"city": "New York",
"state": "NY",
"zip": "10020"
}
}
}
不过,现在让我们假设与 Record 关联的 Schema 指示 address 字段不是 Record 而是一个 Map 字段。在这种情况下,如果我们尝试使用 RecordPath /details/address/zip 引用 zip,RecordPath 将不匹配,因为 address 字段不是 Record,因此没有名为 zip 的子 Record。相反,它是一个 Map 字段,键和值都是 String 类型。不幸的是,在查看 JSON 时,这看起来可能有点令人困惑,因为 JSON 并没有真正的类型系统。但是,当我们将 JSON 转换为 Record 对象以操作数据时,这种区别可能很重要。
在上面描述的情况下,我们仍然可以使用 RecordPath 访问 zip 字段。不过,我们必须使用稍微不同的语法:/details/address['zip']。这告诉 RecordPath 我们想在最高层访问 details 字段。然后我们想访问其 address 字段。由于 address 字段是 Map 字段,我们可以使用方括号来指示我们想指定一个 Map 键,然后可以在引号中指定键。
此外,我们可以使用逗号分隔的列表选择多个 Map 键:/details/address['city', 'state', 'zip']。如果愿意,我们还可以使用通配符运算符(*)选择所有字段:/details/address[*]。Map 字段不包含任何类型的排序,因此不可能通过数字索引引用键。
谓词
到目前为止,我们已经讨论了两种不同类型的过滤器。它们中的每一种都允许我们从允许多个值的字段中选择一个或多个元素。然而,通常我们需要应用一个过滤器来限制选择哪些 Record 字段。例如,如果我们想选择 zip 字段但仅限于 state 不是 New York 的 address 字段呢?上述示例没有提供任何方法来做到这一点。
RecordPath 为用户提供了指定谓词的能力。谓词简单来说就是可以应用于字段以确定该字段是否应包含在结果中的过滤器。与其他过滤器一样,谓词在方括号内指定。谓词的语法为 <相对 RecordPath> <运算符> <表达式>。相对 RecordPath 的工作方式与任何其他 RecordPath 一样,但必须以 .(引用当前字段)或 ..(引用当前字段的父级)开头,而不是斜杠,并引用相对于谓词所应用字段的字段。运算符 必须是以下之一:
-
等于(
=) -
不等于(
!=) -
大于(
>) -
大于或等于(
>=) -
小于(
<) -
小于或等于(
<=)
表达式 可以是字面值,如 50 或 Hello,也可以是另一个 RecordPath。
为了说明这一点,让我们以下面的 Record 为例:
{
"name": "John Doe",
"workAddress": {
"number": "123",
"street": "5th Avenue",
"city": "New York",
"state": "NY",
"zip": "10020"
},
"homeAddress": {
"number": "456",
"street": "Grand St",
"city": "Jersey City",
"state": "NJ",
"zip": "07304"
},
"details": {
"position": "Dataflow Engineer",
"preferredState": "NY"
}
}
现在我们可以使用谓词仅选择 state 不是 New York 的字段。例如,我们可以使用 /[./state != 'NY']。这将选择任何具有 state 字段的 Record 字段,前提是该 state 的值不是 "NY"。请注意,details Record 不会被返回,因为它没有名为 state 的字段。因此在此示例中,RecordPath 将仅选择 homeAddress 字段。一旦我们选择了该字段,就可以继续我们的 RecordPath。正如我们在上面所说的,我们可以选择 zip 字段:/[./state != 'NY']/zip。此 RecordPath 将仅从 homeAddress 字段中选择 zip 字段。
我们还可以将一个字段中的值与另一个字段中的值进行比较。例如,我们可以通过使用 RecordPath /*[./state = /details/preferredState] 选择位于用户偏好 state 的地址。在此示例中,此 RecordPath 将检索 workAddress 字段,因为其 state 字段与 preferredState 字段的值匹配。
此外,我们可以通过使用父级运算符(..)编写引用 state 为 "NJ" 的任何记录的 "city" 字段的 RecordPath:/*/city[../state = 'NJ']。
函数
在上面的 函数用法 部分中,我们描述了如何在 RecordPath 中使用函数以及为什么使用。在这里,我们将描述可用的不同函数、它们的功能以及工作方式。函数可以分为两组:独立函数,可以作为 RecordPath 的"根",如 substringAfter( /name, ' ' );以及 过滤器函数,用作过滤器,如 /name[ contains('John') ]。独立函数也可以在过滤器中使用,但不返回 boolean(true 或 false 值),因此不能本身作为整个过滤器。例如,我们可以使用路径 /name[ substringAfter(., ' ') = 'Doe'],但不能简单地使用 /name[ substringAfter(., ' ') ],因为这样做没有意义,因为过滤器必须是布尔值。
除非另有说明,以下所有示例都编写为对以下 Record 进行操作:
{
"name": "John Doe",
"workAddress": {
"number": "123",
"street": "5th Avenue",
"city": "New York",
"state": "NY",
"zip": "10020"
},
"homeAddress": {
"number": "456",
"street": "Grand St",
"city": "Jersey City",
"state": "NJ",
"zip": "07304"
},
"details": {
"position": "Dataflow Engineer",
"preferredState": "NY",
"employer": "",
"vehicle": null,
"phrase": " "
}
}
独立函数
substring
substring 函数返回字符串值的一部分。该函数需要 3 个参数:要截取一部分的值、从 0 开始的起始索引(包含)和从 0 开始的结束索引(不包含)。起始索引和结束索引可以是 0 表示字符串的第一个字符,正整数表示字符串中的第 n 个索引,或负整数。如果值是负整数,比如 -n,则表示从末尾数第 n 个字符。-1 表示字符串中的最后一个字符。因此,例如 substring( 'hello world', 0, -1 ) 表示取字符串 hello,并返回字符 0 到最后一个字符,所以返回值将是 hello world。
RecordPath |
返回值 |
|
John Doe |
|
John |
|
<空字符串> |
|
John Doe |
|
<空字符串> |
substringAfter
返回字符串值中在某个其他值首次出现之后的部分。
RecordPath |
返回值 |
|
Doe |
|
hn Doe |
|
John Doe |
|
John Doe |
substringAfterLast
返回字符串值中在某个其他值最后一次出现之后的部分。
RecordPath |
返回值 |
|
Doe |
|
e |
|
John Doe |
|
John Doe |
substringBefore
返回字符串值中在某个其他值首次出现之前的部分。
RecordPath |
返回值 |
|
John |
|
J |
|
John Doe |
|
John Doe |
substringBeforeLast
返回字符串值中在某个其他值最后一次出现之前的部分。
RecordPath |
返回值 |
|
John |
|
John D |
|
John Doe |
|
John Doe |
replace
将一个字符串的所有出现替换为另一个字符串。
RecordPath |
返回值 |
|
Jxhn Dxe |
|
Jxyzhn Dxyze |
|
John Doe |
|
John New York |
replaceRegex
对字符串值的内容评估正则表达式,并将任何匹配替换为另一个值。此函数需要 3 个参数:要运行正则表达式的字符串、要运行的正则表达式和替换值。替换值可以选择性地使用反向引用,如 $1 和 ${named_group}。
RecordPath |
返回值 |
|
Jxhn Dxe |
|
Jxyzhn Dxyze |
|
John Doe |
|
John New York |
|
Jxohn Dxoe |
|
Jxohn Dxoe |
replaceNull
当第一个参数非空时返回其值。如果第一个参数评估为 null,该函数返回替换值。
RecordPath |
返回值 |
|
no vehicle |
|
John Doe |
|
John Doe |
concat
将所有参数连接在一起。
RecordPath |
返回值 |
|
John Doe lives in Jersey City |
join
使用分隔符将多个值连接在一起。
RecordPath |
返回值 |
|
123, 5th Avenue, New York, NY, 10020 |
anchored
允许在将根上下文锚定到子记录的同时评估 RecordPath。
RecordPath |
返回值 |
|
Jersey City |
此外,这可以与数组结合使用。例如,如果我们有以下记录:
{
"id": "1234",
"elements": [{
"name": "book",
"color": "red"
}, {
"name": "computer",
"color": "black"
}]
}
我们可以评估以下 RecordPath:
RecordPath |
返回值 |
|
包含 |
|
包含 |
fieldName
通常,当给出到 Record 中特定字段的路径时,返回的是该字段的值。然而,有时获取字段名称而不是值可能很有用。为此,我们可以使用 fieldName 函数。
RecordPath |
返回值 |
|
|
|
Jersey City |
在上面的示例中,第一个 RecordPath 返回两个单独的字段名:"workAddress" 和 "homeAddress"。第二个 RecordPath 则返回 "city" 字段的值,并使用 fieldName 函数作为谓词。第二个 RecordPath 找到一个 "city" 字段,其父级名称不以 "work" 开头。这意味着它将返回父级为 "homeAddress" 的 "city" 字段的值,但不会返回父级为 "workAddress" 的 "city" 字段的值。
toDate
将字符串转换为日期。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "eventDate", "type" : "string"}
]
}
以及如下记录:
{
"name" : "My Event",
"eventDate" : "2017-10-20T00:00:00Z"
}
以下记录路径将把 eventDate 字段解析为日期:
toDate( /eventDate, "yyyy-MM-dd’T’HH:mm:ss’Z'")
toDate( /eventDate, "yyyy-MM-dd’T’HH:mm:ss’Z'", "GMT+8:00")
toString
将值转换为字符串,如果输入类型为 "bytes" 则使用给定的字符集。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "bytes", "type" : "bytes"}
]
}
以及如下记录:
{
"name" : "My Event",
"bytes" : "Hello World!"
}
以下记录路径将把 bytes 字段解析为字符串:
toString( /bytes, "UTF-8")
toBytes
使用给定的字符集将字符串转换为 byte[]。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "s", "type" : "string"}
]
}
以及如下记录:
{
"name" : "My Event",
"s" : "Hello World!"
}
以下记录路径将使用 UTF-16 编码将字符串字段转换为字节数组:
toBytes( /s, "UTF-16")
coalesce
从给定参数中返回第一个非空值。例如,给定如下记录:
{
"id": null,
"name": "John Doe"
}
以下记录路径将返回 "John Doe":
coalesce(/id, /name)
给定记录:
{
"id": "1234",
"name": null
}
相同的记录路径将返回 "1234"。
给定记录:
{
"id": null,
"name": null
}
记录路径将返回 null。
给定记录:
{
"id": "null",
"name": "John Doe"
}
记录路径将返回字符串 "null"。请注意这里非常重要的区别,即 id 字段没有 null 值,而是该字段的值是字面字符串 "null"。
给定记录:
{
"name": null
}
记录路径将返回 null。由于 id 字段不存在,它被视为 null 值。
给定记录:
{
"id": "1234",
"name": "John Doe"
}
记录路径将返回 "1234"。但是,记录路径 coalesce(/name, /id) 将返回 "John Doe",因为给定的两个字段都非空,所以 coalesce 函数返回其参数中引用的第一个值,而不是 Record 本身中遇到的第一个值。
format
将日期转换为给定格式的字符串,可选时区。当不提供第二个参数时,该函数默认使用系统本地时区。
此函数的第一个参数必须是 Date 或 Number,第二个参数必须是遵循 Java DateTimeFormatter 的格式字符串,第三个参数(可选)必须是格式字符串,可以是缩写(如 "PST")、全名(如 "America/Los_Angeles")或自定义 ID(如 "GMT-8:00")。
例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "eventDate", "type" : { "type" : "long", "logicalType" : "timestamp-millis" } }
]
}
以及如下记录:
{
"name" : "My Event",
"eventDate" : 1508457600000
}
以下记录路径表达式将把日期格式化为字符串:
RecordPath |
返回值 |
|
2017-10-20T00:00:00Z |
|
2017-10-20 |
|
2017-10-20 08:00:00 +0800 |
|
2017-10-20 |
如果字段声明为 String,则必须在格式化之前调用 toDate 函数。
例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "eventDate", "type" : "string"}
]
}
以及如下记录:
{
"name" : "My Event",
"eventDate" : "2017-10-20T00:00:00Z"
}
以下记录路径表达式将重新格式化日期字符串:
RecordPath |
返回值 |
|
2017-10-20 |
trim
删除字符串开头和结尾的空格。
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" }
]
}
以及如下记录:
{
"name" : " John Smith "
}
以下记录路径表达式将删除多余的空格:
RecordPath |
返回值 |
|
John Smith |
toUpperCase
将整个字符串转换为大写。
{
"type": "record",
"name": "events",
"fields": [
{ "name": "fullName", "type": "string" }
]
}
以及如下记录:
{
"fullName" : "john smith"
}
以下记录路径表达式将小写字母转换为大写:
RecordPath |
返回值 |
|
JOHN SMITH |
toLowerCase
将整个字符串转换为小写。
{
"type": "record",
"name": "events",
"fields": [
{ "name": "message", "type": "string" }
]
}
以及如下记录:
{
"name" : "hEllO wORLd"
}
以下记录路径表达式将大写字母转换为小写:
RecordPath |
返回值 |
|
hello world |
base64Encode
使用 UTF-8 字符集将字符串或 byte[] 进行 Base64 编码。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" }
]
}
以及如下记录:
{
"name" : "John"
}
以下记录路径表达式将使用 Base64 编码字符串:
RecordPath |
返回值 |
|
Sm9obg== |
base64Decode
解码 Base64 编码的字符串或 byte[]。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" }
]
}
以及如下记录:
{
"name" : "Sm9obg=="
}
以下记录路径表达式将使用 Base64 解码字符串:
RecordPath |
返回值 |
|
John |
escapeJson
使用 UTF-8 字符集将 Record、Array 或简单字段(如 String)转为 JSON 字符串。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [{
"name": "person",
"type": "record",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "age", "type": "int" }
]
}]
}
以及如下记录:
{
"person": {
"name" : "John",
"age" : 30
}
}
以下记录路径表达式将把记录转换为转义的 JSON 字符串:
RecordPath |
返回值 |
|
"{\"person\":{\"name\":\"John\",\"age\":30}}" |
|
"\"John\"" |
|
"30" |
unescapeJson
使用 UTF-8 字符集将 JSON 字符串化的元素转换为 Record、Array 或简单字段(如 String)。
此函数最多接受三个参数:
-
要解析的 JSON 字符串化元素的 Record Path2. (可选)是否将解析的 JSON 对象从 Map 转换为 Record,默认 =
false`3. (可选)是否递归地将嵌套的 JSON 对象从 Map 转换为 Record,默认 = `false
注意:RecordSetWriter 无法序列化 Map,因此如果不转换为 Record,字段可能会被省略。例如,在 UpdateRecord 中使用 unescapeJson 替换 Record 的根("/")而不使用 Record 转换,将导致空输出,因为 RecordSetWriter 无法将结果 Map 内容匹配到 RecordSchema。
例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [{
"name": "person",
"type": "record",
"fields": [
{ "name": "name", "type": "string" },
{ "name": "age", "type": "int" }
]
}]
}
以及如下记录:
{
"json_str": "{\"person\":{\"name\":\"John\",\"age\":30}}"
}
以下记录路径表达式将用取消转义的 JSON 字段填充记录:
RecordPath |
返回值 |
|
{"person": {"name": "John", "age": 30}}" |
给定如下记录:
{
"json_str": "{\"name\":\"John\",\"age\":30}"
}
以下记录路径表达式将返回:
RecordPath |
返回值 |
|
{"name": "John", "age": 30}(作为 Record) |
|
{"name"="John", "age"=30}(作为 Map) |
|
{"name"="John", "age"=30}(作为 Map) |
给定如下记录:
{
"json_str": "{\"name\":\"John\",\"age\":30,\"addresses\"[{\"address_1\": \"123 Fake Street\"}]}"
}
以下记录路径表达式将返回:
RecordPath |
返回值 |
|
{"name"="John", "age"=30, "addresses"=[{"address_1"="123 Fake Street"}]}(作为 Map,"addresses" 中的每个条目也是 Map) |
|
{"name": "John", "age": 30, "addresses"=[{"address_1"="123 Fake Street"}]}(作为 Record,"addresses" 中的每个条目是 Map) |
|
{"name": "John", "age": 30, "addresses": [{"address_1": "123 Fake Street"}]}(作为 Record,"addresses" 中的每个条目也是 Record) |
给定如下记录:
{
"json_str": "\"John\""
}
以下记录路径表达式将返回:
RecordPath |
返回值 |
|
"John" |
注意,如果要取消转义的 JSON 要设置在 Record 的字段中,则必须预先定义目标模式 - 推断模式目前不会自动执行此操作。
hash
使用哈希算法转换字符串。例如,给定如下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" }
]
}
以及如下记录:
{
"name" : "John"
}
以下记录路径表达式将使用以下算法之一对字符串进行哈希:[SHA-384, SHA-224, SHA-256, MD2, SHA, SHA-512, MD5]。
RecordPath |
返回值 |
|
527bd5b5d689e2c32ae974c6229ff785 |
padLeft
在输入字符串前面添加字符,直到达到所需长度。
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" }
]
}
以及如下记录:
{
"name" : "john smith"
}
以下记录路径表达式将在输入字符串前面添加 '@' 字符:
RecordPath |
返回值 |
|
@@@@@john smith |
padRight
在输入字符串后面添加字符,直到达到所需长度。
{
"type": "record",
"name": "events",
"fields": [
{ "name": "name", "type": "string" }
]
}
以及如下记录:
{
"name" : "john smith"
}
以下记录路径表达式将在输入字符串后面添加 '@' 字符:
RecordPath |
返回值 |
|
john smith@@@@@ |
uuid5
将 UUID v5 插入目标字段。
此函数有两种使用方式:使用或不使用命名空间。给定以下模式:
{
"type": "record",
"name": "events",
"fields": [
{ "name": "input", "type": "string" },
{ "name": "id_ns", "type": "string" }
]
}
以及如下记录:
{
"input" : "john smith",
"id_ns": "02b317d3-7fec-421a-89c5-3ad0eb83c79e"
}
此函数有两种使用方式:
uuid5(/input)
uuid5(/input, /id_ns)
第一个选项将生成一个简单的 UUID v5,在生成过程中不使用命名空间。第二个将获取所提供记录路径的值并将其用作命名空间。
请注意,命名空间必须始终是有效的 UUID 字符串。空字符串、其他数据类型等将导致错误。这是设计使然,因为 UUID v5 最常见的用例是跨数据集唯一标识记录。
count
返回元素数量。这通常与数组结合使用。例如,如果我们有以下记录:
{
"id": "1234",
"elements": [{
"name": "book",
"color": "red"
}, {
"name": "computer",
"color": "black"
}]
}
我们可以使用 count 函数确定 elements 的数量。使用:
count(/elements[*])
将得到值 2。我们还可以将其用作过滤器,如:
/id[ count(/elements[*]) = 2 ]
这将返回值为 1234 的 id 元素。
mapOf
使用给定参数创建字符串的 Map。例如,如果我们有以下记录:
{
"firstName": "John",
"lastName": "Snow"
}
我们可以使用 UpdateRecord 处理器配合
/fullName => mapOf("firstName", /firstName, "lastName", /lastName)
这将给我们类似:
{
"firstName": "John",
"lastName": "Snow",
"fullName": {"firstName": "John", "lastName": "Snow"}
}
此函数需要偶数个参数,记录路径必须表示简单字段值。
recordOf
使用给定参数创建嵌套记录。例如,如果我们有以下记录:
{
"firstName": "Alice",
"lastName": "Koopa",
"age": 30,
"hobbies": ["reading", "hiking", "coding"],
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA"
}
}
我们可以使用 UpdateRecord 处理器配合
/profile => recordOf("name", /firstName, "location", /address/city, "hobbies", /hobbies, /age, "years old")
这将给我们类似:
{
"name": "Alice",
"hobbies": ["reading", "hiking", "coding"],
"location": "Anytown",
"30": "years old"
}
此函数需要偶数个参数。每对参数类似于新记录中的一个字段。每个奇数参数(每对的第一个)用作字段名并强制转换为字符串值。每个偶数参数(每对的第二个)用作字段值。
arrayOf
从多个值创建数组。
{
"id": "1234",
"elements": [{
"name": "book",
"color": "red"
}, {
"name": "computer",
"color": "black"
}]
}
我们可以使用 arrayOf 函数结合 unescapeJson 函数向 elements 数组追加一个新元素。给定此示例输入,arrayOf(/elements[*], unescapeJson('{"name":"phone","color":"blue"}')) 的 RecordPath 将返回以下数组:
[{
"name": "book",
"color": "red"
}, {
"name": "computer",
"color": "black"
}, {
"name": "phone",
"color": "blue"
}]
我们还可以使用 arrayOf 函数向数组追加多个元素。给定输入记录:
{
"name": "John Doe",
"friends": ["Jane", "Jack", "Jill"]
}
arrayOf(/friends[*], 'Joe', 'Jim', 'Jeremy') 的 RecordPath 将返回以下数组:
["Jane", "Jack", "Jill", "Joe", "Jim", "Jeremy"]
我们还可以简单地从任意值创建数组,如 arrayOf('good-bye', 'adios', 'au revoir') 将返回以下数组:
["good-bye", "adios", "au revoir"]
过滤器函数
contains
如果字符串值包含所提供的子字符串则返回 true,否则返回 false。
RecordPath |
返回值 |
|
John Doe |
|
<不返回结果> |
|
John Doe |
containsRegex
对字符串值的内容评估正则表达式,如果字符串值包含匹配正则表达式的子字符串则返回 true,否则返回 false。此函数需要 2 个参数:要运行正则表达式的字符串和要运行的正则表达式。
RecordPath |
返回值 |
|
John Doe |
|
John Doe |
|
<不返回结果> |
matchesRegex
对字符串值的内容评估正则表达式,如果正则表达式完全匹配字符串值则返回 true,否则返回 false。此函数需要 2 个参数:要运行正则表达式的字符串和要运行的正则表达式。
RecordPath |
返回值 |
|
John Doe |
|
<不返回结果> |
|
John Doe |
startsWith
如果字符串值以所提供的子字符串开头则返回 true,否则返回 false。
RecordPath |
返回值 |
|
John Doe |
|
<不返回结果> |
|
<不返回结果> |
|
John Doe |
endsWith
如果字符串值以所提供的子字符串结尾则返回 true,否则返回 false。
RecordPath |
返回值 |
|
John Doe |
|
<不返回结果> |
|
<不返回结果> |
|
John Doe |
not
反转传递给 not 函数的函数或表达式的值。
RecordPath |
返回值 |
|
John Doe |
|
John Doe |
|
<不返回结果> |
数学函数
数学函数接受以下数字类型:
-
Byte
-
Short
-
Integer
-
Long
-
Float
-
Double
如果可以强制转换为数字,还接受以下非数字类型:
-
String
-
Date
除非另有指定,计算结果为 Long 或 Double:
-
如果任何参数是 Float 或 Double,结果为 Double
-
否则,结果为 Long
数学函数不处理溢出。
以下示例使用如下模式:
{
"type": "record",
"name": "logs",
"fields": [
{ "name": "flags", "type": "byte" },
{ "name": "seq", "type" : "short"},
{ "name": "txns", "type" : "int"},
{ "name": "timestamp", "type" : "long"},
{ "name": "temp", "type" : "float"},
{ "name": "load", "type" : "double"},
{ "name": "timestampMillis", "type" : "string"},
{ "name": "date", "type" : "date"},
]
}
以及如下记录:
{
"flags" : 7,
"seq": 1024,
"txns": 125000,
"timestamp": 1768967368,
"temp": 55.0,
"load": 5.67,
"timestampMillis": "1768967368000",
"date": "2026-01-21",
}
toNumber
将字符串或日期值强制转换为数字。如果参数已经是数字,则原样返回,保留原始类型。
RecordPath |
返回值 |
|
456 |
|
7 |
|
5.67 |
|
7.89 |
|
1768967368000 |
|
1768953600000 |
add
计算两个数字的和。
RecordPath |
返回值 |
|
4 |
|
4.0 |
|
9 |
|
57.0 |
|
60.67 |
subtract
计算两个数字的差。
RecordPath |
返回值 |
|
0 |
|
0.0 |
|
5 |
|
-53.0 |
|
-49.33 |