ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Apache Druid JSON Flatten Spec 完全指南:在摄入期将嵌套 JSON 字段扁平化为列

Apache Druid JSON Flatten Spec 完全指南:在摄入期将嵌套 JSON 字段扁平化为列 数据库数据分析OLAP大数据实时分析数据仓库后端【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址https://gitcode.com/gh_mirrors/druid7/druid点击查看免费下载JSON Flatten Spec扁平化规范是 Apache Druid 在数据摄入ingestion阶段对嵌套 JSON 输入进行字段展开的核心配置。本文基于仓库中的官方文档 flatten-json.md并结合api与java-util模块中JSONParseSpec、JSONPathSpec、JSONPathParser等类的源码实现系统讲解 flattenSpec 的字段语义、配置写法、自动字段发现规则、聚合器配合方式及底层运行原理帮助你为批式与流式摄入任务编写出正确、高效、可复现的扁平化配置。什么是 JSON Flatten Spec为什么需要它Druid 中的列column来源于摄入时的平面化输入行每个输入字段对应一个维度列或指标列。然而真实的业务事件如点击流、埋点日志、Kafka 中的订单事件常常是嵌套 JSON例如{ foo: {bar: abc}, nestmet: {val: 42}, thing: {food: [sandwich, pizza]}, world: [{hey: there}, {tree: apple}] }如果直接摄入foo、thing这类 Map 值无法成为普通列。JSON Flatten Spec 正是为了解决这个问题它允许在摄入期ingestion time将嵌套 JSON 字段拉平让$.foo.bar这样的深层值直接成为独立的列foo.bar同时可以重命名、选取数组中的特定元素等。需要强调的一个前提是只有 JSON 格式的 ParseSpecformat: json支持扁平化其他格式如 CSV、TSV不适用。这一点在 flatten-json.md 中有明确说明也体现在源码中flattenSpec 是JSONParseSpec独有的属性见 JSONParseSpec.java。flattenSpec 的两个顶层配置项flattenSpec 挂载在parseSpec之下包含两个可选字段完整参数定义如下FieldTypeDescriptionRequireduseFieldDiscoveryBoolean若为 true则将根级别root level所有单值字段非 map 或 list以及扁平列表单值列表自动解释为列。no默认 truefieldsJSON Object 数组指定感兴趣的字段及其访问方式。no默认 []从源码看这两个默认值由 JSONPathSpec.java 实现this.useFieldDiscovery useFieldDiscovery null ? true : useFieldDiscovery; this.fields fields null ? ImmutableList.JSONPathFieldSpecof() : fields;即省略useFieldDiscovery时默认开启字段自动发现省略fields时为空列表。也就是说一个最简的 JSON parseSpec 不写 flattenSpec 也能工作——此时等价于{useFieldDiscovery: true, fields: []}所有根级单值字段都会被自动发现这正是普通非嵌套 JSON 摄入的默认行为。更进一步JSONParseSpec的构造器在 flattenSpec 为 null 时也会自动回退为new JSONPathSpec(true, null)见 JSONParseSpec.java。JSON Field Specfields 数组中每个字段的语义fields是 JSON 对象数组每个对象描述一个目标字段的名字和访问路径FieldTypeDescriptionRequiredtypeString字段类型root 或 path。yesnameString该字符串将作为数据摄入后的列名。yesexprString定义访问 JSON 对象内字段的表达式使用 JsonPath 记法该仓库通过 jayway JsonPath 库实现。仅 type 为 path 时使用否则被忽略。仅 type 为 path 时需要三种字段类型的行为对应关系为type: root直接取 JSON 根对象的某个键expr被忽略。对应源码 JSONPathParser.java 中的document.get(fieldName)直接取值分支。type: path通过 JsonPath 表达式如$.foo.bar、$.hello[0]访问深层或数组元素取值分支为path.read(document, jsonPathConfig)。便捷写法定义根级字段时可以直接用字符串代替完整对象例如dim2等价于{type: root, name: dim2}。这一行为由 JSONPathFieldSpec.java 中的JsonCreator fromString(String name)实现它会把字符串反序列化为JSONPathFieldSpec.createRootField(name)即 typeROOT、exprnull 的对象。需要留意的是JsonPath 表达式只在字段被显式定义时才会被编译。JSONPathParser在构造时对每个 PATH 类型字段调用JsonPath.compile(fieldSpec.getExpr())见 JSONPathParser.java因此非法表达式会在解析器初始化阶段就暴露。完整示例从嵌套事件到扁平列文档给出了一个典型的事件 JSON。假设输入事件形如{ timestamp: 2015-09-12T12:10:53.155Z, dim1: qwerty, dim2: asdf, dim3: zxcv, ignore_me: ignore this, metrica: 9999, foo: {bar: abc}, foo.bar: def, nestmet: {val: 42}, hello: [1.0, 2.0, 3.0, 4.0, 5.0], mixarray: [1.0, 2.0, 3.0, 4.0, {last: 5}], world: [{hey: there}, {tree: apple}], thing: {food: [sandwich, pizza]} }按文档的设计意图metrica是 Long 型指标列hello是 Double 数组指标列nestmet.val是嵌套的 Long 指标列其余字段是维度列。对应的 parseSpec 完整定义如下这是本文的核心可复制配置来自原文档并保持原样parseSpec: { format: json, flattenSpec: { useFieldDiscovery: true, fields: [ { type: root, name: dim1 }, dim2, { type: path, name: foo.bar, expr: $.foo.bar }, { type: root, name: foo.bar }, { type: path, name: path-metric, expr: $.nestmet.val }, { type: path, name: hello-0, expr: $.hello[0] }, { type: path, name: hello-4, expr: $.hello[4] }, { type: path, name: world-hey, expr: $.world[0].hey }, { type: path, name: worldtree, expr: $.world[1].tree }, { type: path, name: first-food, expr: $.thing.food[0] }, { type: path, name: second-food, expr: $.thing.food[1] } ] }, dimensionsSpec : { dimensions : [], dimensionsExclusions: [ignore_me] }, timestampSpec : { format : auto, column : timestamp } }对这个示例做逐字段拆解dim1与dim2根级字段的两种写法。dim1使用完整对象{type: root, name: dim1}dim2使用便捷字符串写法两者等价。两个foo.bar这里演示了一个非常有用的技巧——事件中同时存在foo.bar这样的字面根键值为def和嵌套路径foo.bar值为abc。通过{type: path, name: foo.bar, expr: $.foo.bar}与{type: root, name: foo.bar}两条定义可以分别把深层值和根级同名键都映射到名为foo.bar的列上注意两者不能同时被自动发现需要显式指定。path-metric←$.nestmet.val把嵌套对象中的val提出来重命名成一个新列名之后可以作为 Long 指标参与聚合。hello-0/hello-4←$.hello[0]/$.hello[4]JsonPath 支持数组下标访问。这里取hello数组的第 1 个和第 5 个元素作为独立列说明数组不一定要整体摄入可以按元素选取。world-hey←$.world[0].hey、worldtree←$.world[1].tree对象数组的访问$.world[i].key取第 i 个元素中的指定键。first-food/second-food←$.thing.food[0]/$.thing.food[1]嵌套数组的组合访问。字段dim3、ignore_me、metrica因为useFieldDiscovery为 true 会被自动发现因此不必出现在 field spec 列表中ignore_me虽被自动发现但通过dimensionsExclusions: [ignore_me]被显式排除。useFieldDiscovery自动字段发现的精确语义自动发现是 flattenSpec 中最重要的行为开关其精确语义如下只自动发现根级单值字段即值不是 map 也不是 list 的字段。上面的示例中dim1、dim2、dim3、ignore_me、metrica、foo.bar根级字面键都会被自动检测为列。扁平列表单值列表也会被自动发现hello是 Double 列表会被自动发现但示例中为了分别摄入各元素仍显式定义了hello-0、hello-4等字段。值为 map 的字段不会被自动发现world必须显式定义因为其值是 map 数组。类似但包含 map 的列表不会被自动发现mixarray与hello表面相似但最后一个元素是 map因此也必须显式定义。这些规则在源码中有清晰的对应实现。JSONPathParser.java 的discoverFields方法if (val null) continue; // null 值跳过 if (val instanceof Map) continue; // map 不自动发现 if (val instanceof List) { if (!isFlatList((List) val)) continue; // 非扁平列表不自动发现 } map.put(field, valueConversionFunction(val));其中isFlatList递归检查列表中是否包含子对象或子列表只有全部为单值的列表才算扁平列表。discoverFields还有一个保护逻辑if (!map.containsKey(field))——已经显式定义的字段不会被重复加入见 JSONPathParser.java。重复定义与输入约束文档明确了两条硬性约束它们同样可以在源码中找到依据不允许重复字段定义否则抛出异常。generateFieldPaths使用LinkedHashMap维护字段映射插入前检查if (map.get(fieldName) ! null) throw new IllegalArgumentException(Cannot have duplicate field definition: fieldName)见 JSONPathParser.java。注意这里对重复的判断基于name因此示例中两个foo.bar会直接触发异常——上面的示例配置里两个foo.bar同名在实际运行时二者只能保留其一请根据业务二选一原文档保留了两条定义用于说明 root 与 path 的并存场景实际使用时需要避免同名冲突。JSON 输入根节点必须是对象不能是数组。{valid: true}、{valid:[1,2,3]}支持而[{invalid: true}]、[1,2,3]不支持。源码中parse()使用mapper.readValue(input, new TypeReferenceMapString, Object(){})把输入反序列化为 Map数组根节点会在此处失败见 JSONPathParser.java失败时抛出ParseExceptionUnable to parse row。此外从源码看还有一个值得注意的细节JsonPath 求值配置了Option.SUPPRESS_EXCEPTIONS见 JSONPathParser.java这意味着当某条 path 表达式在当前事件中取不到值时例如$.thing.food[1]不存在解析不会抛异常而是返回 null 并被跳过不会导致整行摄入失败。这保证了同一份 schema 可以兼容字段缺失的稀疏事件。值类型转换flat 之后的类型处理JSONPathParser在把取值放入输出 Map 前会统一做valueConversionFunction转换见 JSONPathParser.java这对理解为什么 JSON 数字能成为 Long/Double 指标很关键Integer→LongJackson 默认把小整数反序列化为IntegerDruid 统一提升为Long便于longSum等聚合。BigInteger→Double大整数转为 Double避免精度截断问题同时说明超大整数不建议作为精确 Long 指标摄入。String→ 经过charsetFix处理保证 UTF-8 可编码。List/Map递归应用同样的转换规则。聚合器metricsSpec如何引用扁平化后的列扁平化发生在摄入parse阶段产出的是普通列名后续dimensionsSpec、metricsSpec、timestampSpec都以扁平化后的列名为准。文档强调聚合器应使用 flattenSpec 中定义的指标列名。沿用上面的示例metricsSpec : [ { type : longSum, name : path-metric-sum, fieldName : path-metric }, { type : doubleSum, name : hello-0-sum, fieldName : hello-0 }, { type : longSum, name : metrica-sum, fieldName : metrica } ]这里fieldName引用的path-metric、hello-0、metrica正是 flattenSpec 中定义的 name 或自动发现的根级字段名。整个摄入任务的数据流为原始 JSON 事件 → JSONPathParser 按 flattenSpec 拉平 → InputRow含维度与指标 → 聚合/索引。JSONParseSpec.makeParser()把JSONPathSpec转换为JSONPathParser.FieldSpec列表并交给JSONPathParser见 JSONParseSpec.java与这一流程一一对应。源码、测试与基准验证与参考仓库中与本主题相关的可继续深挖的代码与测试包括配置模型JSONPathSpecJSONPathSpec.java与JSONPathFieldSpecJSONPathFieldSpec.java分别对应 flattenSpec 顶层结构与 field spec 对象其JsonCreator反序列化逻辑保证了 JSON 配置到 Java 对象的映射。解析实现JSONPathParser.java 是扁平化的真正执行者涵盖 path 编译、字段发现、类型转换、重复检测等全部逻辑。序列化/反序列化测试JSONPathSpecTest.java 验证了 flattenSpec 配置的 JSON 往返序列化其中同时构造了嵌套 path 字段foobar1$.foo.bar1与根字段foo.bar1并断言二者的name与expr正确存取InputRowParserSerdeTest.java 则覆盖了含 flattenSpec 的 parseSpec 整体序列化链路。性能基准FlattenJSONProfile.java 与 FlattenJSONBenchmarkUtil.java 提供了对嵌套 JSON 解析含扁平化的 JMH 基准可用来评估不同字段数量、嵌套深度下的解析吞吐适合在引入大量 path 字段前做性能验证。使用建议与注意事项汇总综合文档与源码在真实摄入任务中使用 flattenSpec 时建议遵循以下要点能自动发现就不显式定义保持useFieldDiscovery: true只对需要重命名、深层访问、数组取元素的字段显式声明可显著减少配置量并降低出错面。同名列冲突要当心fields中每个name必须唯一源码会抛IllegalArgumentException。若事件中同时存在foo.bar字面键与嵌套foo.bar需要规划好列名例如分别命名为foo.bar与root_foo.bar不要照抄文档示例中的同名写法。map 与对象数组必须显式声明自动发现不会覆盖它们漏配会导致这些字段静默丢失。path 表达式要验证建议先在独立工具中验证 JsonPath 表达式对典型样本事件的求值结果再写入配置同时注意SUPPRESS_EXCEPTIONS行为意味着取值失败会返回空而不报错配置错误可能难以在日志中直接发现。输入根节点必须是 JSON 对象数组根节点如 Kafka 中直接发送[...]无法直接摄入需要在上游做包装或预处理。指标列引用扁平化后的 namemetricsSpec.fieldName、dimensionsSpec都以 flattenSpec 产出的列名为准。延伸阅读本文档原文flatten-json.md摄入任务整体配置tasks.md 与 index.md维度与指标定义schema-design.md流式摄入中应用 flattenSpec 的示例配置kafka-ingestion.md赞分享数据库数据分析OLAP大数据实时分析数据仓库后端【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址https://gitcode.com/gh_mirrors/druid7/druid点击查看免费下载相关推荐Normalizr 简介用 Schema 将深层嵌套 JSON 规范化为扁平实体字典Normalizr 简介用 Schema 将深层嵌套 JSON 规范化为扁平实体字典 Normalizr 是一个小巧但强大的 JavaScript 工具库它前端normalizr 快速上手用 schema 将嵌套 JSON 规范化为扁平实体字典normalizr 快速上手用 schema 将嵌套 JSON 规范化为扁平实体字典 阅读完本指南你将掌握 normalizr 的核心工作流为嵌套 JSO前端tinygrad重新定义深度学习框架的极简主义技术架构tinygrad重新定义深度学习框架的极简主义技术架构 在深度学习框架日益复杂化的今天tinygrad以不到10,000行代码的极简设计为技术决策者提供了数据库OLAP大数据后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表