☰
MooTool 跨端 JSONPath 选择器预览对齐:Compose 端 `formatPreview` 语义迁移与源码解析
2026/10/15 6:37:42 网站建设 项目流程
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载

本篇技术指南以 DIFF-430 文档 为核心,解析 MooTool 下一代 Compose(Desktop)实现如何将 JSONPath 路径选择器与检查器内联树的节点预览,对齐 Electron 端JsonPathPicker的formatPreview行为,并理清「路径预览」与「JSONPath 查询」两条链路在字符串标量上的格式化差异。读完你将掌握:两条链路的输出规则与实现位置、单元素数组解包等边界行为,以及对应的单测与离线验证命令。

一、问题背景:预览区字符串为什么带引号

在 MooTool 的 Electron 实现中,JsonPathPicker弹层的右侧预览区调用formatPreview(value)渲染选中路径的节点值(见 JsonPathPicker.tsx):

  • 值为undefined(路径不存在)时,显示空字符串;
  • 值为字符串标量时,显示原文(例如"One"只显示One,不带 JSON 引号);
  • 值为对象/数组时,使用JSON.stringify(value, null, 2)缩进美化输出。

而 Compose 版本在 DIFF-430 之前,路径预览走的是queryPath/formatPathResult链路。这条链路为了与 Electron 的查询结果formatJsonPathValue对齐(见 jsonTools.ts),对字符串标量执行JSON.stringify(value),于是"One"会显示成带引号的"One"。这造成了同一份数据在弹层、内联树与查询结果之间显示不一致:预览区出现了 JSON 语法形态的引号,而 Electron 的预览区是"所见即所得"的原始文本。

二、行为定义:预览与查询各走各的格式化

DIFF-430 给出的最终行为划分清晰而明确:

场景入口字符串标量对象 / 数组路径不存在
路径选择器/内联树预览JsonEngine.pathPickerPreview+formatPickerPreview原文(无 JSON 引号)pretty-print 缩进空字符串
JSONPath查询JsonEngine.queryPath+formatPathResult带 JSON 引号(JSON.stringify)pretty-print 缩进"undefined"

要点是:预览对齐 ElectronformatPreview;查询仍保持对齐 ElectronformatJsonPathValue,两者都是 Electron 侧的既有约定,Compose 侧只需把预览链路切换到新的实现即可。

三、Compose 源码级解析:pathPickerPreview与formatPickerPreview

核心实现位于 JsonEngine.kt,pathPickerPreview与formatPickerPreview两个函数共同构成新的预览链路。

fun pathPickerPreview(input: String, path: String, t: JsonTranslator): String { if (path.isBlank()) return "" val document = parsePreserving(input, t) return try { formatPickerPreview(JsonPath.using(jsonPathConfig).parse(document).read<JsonNode>(path.trim())) } catch (_: PathNotFoundException) { "" } catch (error: InvalidPathException) { throw JsonException(t.t("json.error.invalidPath", mapOf("reason" to (error.message ?: path)))) } catch (error: JsonPathException) { throw JsonException(t.t("json.error.invalidPath", mapOf("reason" to (error.message ?: path)))) } } private fun formatPickerPreview(result: JsonNode?): String { if (result == null || result.isMissingNode) return "" val node = if (result.isArray && result.size() == 1) result[0] else result if (node == null || node.isMissingNode) return "" if (node.isTextual) return node.textValue() return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node) }

对照可提炼出四个关键行为:

  1. 空路径与空结果都返回空字符串:path.isBlank()直接返回"";PathNotFoundException(路径不存在)捕获后同样返回""——与 ElectronformatPreview中value === undefined返回''语义一致。
  2. 字符串标量去引号:node.isTextual时直接取node.textValue()原始文本,不再经过 Jackson 的writeValueAsString,从而去掉 JSON 引号。
  3. 对象/数组走 pretty-printer:非文本节点交给mapper.writerWithDefaultPrettyPrinter(),等价于 Electron 的JSON.stringify(value, null, 2),输出带缩进的格式化 JSON。
  4. 单元素数组解包:if (result.isArray && result.size() == 1) result[0],把仅含一个元素的数组退化为其唯一元素再格式化。这与查询链路formatPathResult中的解包逻辑完全一致,也与JsonEngine的 Jayway JSONPath 配置Option.ALWAYS_RETURN_LIST(见 JsonEngine.kt)互相配合——查询始终以列表形态返回,格式化前统一解包,保证两条链路对同一路径输出一致的数据形态。

值得留意的是formatPickerPreview与formatPathResult的对照:formatPathResult 对文本节点执行mapper.writeValueAsString(value.textValue())(输出带引号的"One"),缺失节点返回字面量"undefined";两条函数仅在文本分支与缺失分支上有意区分,这正是 DIFF-430 的核心差异点。

四、jsonPathNodePreview:预览链路的 UI 统一入口

pathPickerPreview是引擎层能力,UI 层统一通过jsonPathNodePreview调起,定义于 JsonInspectorResult.kt:

internal fun jsonPathNodePreview( input: String, path: String, translator: JsonTranslator, fallback: String = "", ): String = runCatching { JsonEngine.pathPickerPreview(input, path, translator) } .getOrElse { fallback }

该 helper 用runCatching包裹引擎调用,任何解析/求值异常都会回退到调用方传入的fallback(通常是树行原有的entry.preview缩写)。DIFF-430 之前,jsonPathNodePreview内部走的是JsonEngine.queryPath(带引号语义),本次迁移后改走pathPickerPreview,从而让所有 UI 预览点一次性切换。

它的三个实际消费方均在 JSON 工具页内:

  • 路径选择弹层右侧预览:见 JsonPathPickerDialog.kt,previewText通过remember(session.editor.revision, previewPath)缓存,随编辑器修订号与选中路径失效重算。
  • 检查器内联路径树:JsonScreen中仅对选中行计算selectedPathPreview(见 JsonScreen.kt),单击行时将预览写入结果区;双击行则先预览再走performInlinePathTreeDoubleTapQuery执行真实查询(见 JsonInspectorResult.kt)。
  • 重复键路径同步:结构面板命中重复键时,jsonInspectorDuplicatePathClick复用jsonPathNodePreview生成预览并写入会话,再复制路径(见 JsonInspectorPathUi.kt)。

五、会话层行为:预览与查询互不污染

DIFF-430 的迁移离不开会话层两个函数的既有约定,均在 JsonInspectorResult.kt:

  • applyPathPickerChoice(弹层「使用」/双击共用):仅写入jsonPath、清空查询结果、设置pathApplied通知并关闭弹层,不执行 JSONPath 查询——这是 DIFF-319 对齐 Electron 的产物。
  • applyInlinePathTreePreview(内联树单击):写入路径与预览到pathResult,同样不触发查询。

正因为弹层"只用不查"、内联树"单击预览、双击查询"的语义早已收敛,DIFF-430 才得以让预览链路独立切换为formatPickerPreview而无副作用;查询语义继续由queryPath/formatPathResult承担,保证结果区与历史记录中的查询输出仍是带引号的 JSON 形态。

六、单测验证与离线运行

新增与调整的测试集中在 JsonPathNodePreviewTest.kt,其中与本主题直接相关的断言包括:

  • preview_uses_pretty_json_for_objects:对$.store这类对象路径,预览包含键名与换行(pretty-print 生效);
  • preview_string_scalar_matches_electron_formatPreview:对$.store.books[0].title路径,字符串标量预览断言等于"One"原文而非"\"One\"",正是 DIFF-430 的命名与验收标准;
  • pathPickerPreview_empty_when_path_missing:对$.missing断言返回空字符串;
  • preview_falls_back_when_json_invalid:输入非法 JSON(如{)时,jsonPathNodePreview返回调用方传入的 fallback。

验证命令与文档一致,在仓库next-compose目录下离线执行:

./gradlew :composeApp:desktopTest --offline

七、演进脉络:从 DIFF-329 到 DIFF-430

把相邻变更记录串联起来,可以还原这条预览链路的完整演进史(均为next-compose/docs/diff/下文档):

  1. DIFF-329 引入jsonPathNodePreview,但经由queryPath(带引号),首次统一弹层与pathResult;
  2. DIFF-330 让内联路径树选中行也走jsonPathNodePreview,替换listPaths的{n}/[n]缩写;
  3. DIFF-331 改为仅对选中行求值,并用remember(editor, jsonPath)缓存单条预览,规避大 JSON 下每条节点都查询的成本;
  4. DIFF-430 即本篇主题:把预览链路从queryPath切换为新增的pathPickerPreview/formatPickerPreview,彻底解决字符串标量预览带引号的问题。

八、适用前提与边界说明

  • 本文描述的是 MooTool 下一代Compose Desktop(next-compose)实现;Electron 参考实现在next/src/features/json/下,两者通过formatPreview语义对齐而非共享代码。
  • pathPickerPreview对非法路径(InvalidPathException/JsonPathException)仍抛出JsonException,由 UI 层jsonPathNodePreview的runCatching转译为 fallback 展示;只有PathNotFoundException被捕获后静默返回空串。
  • 预览链路与查询链路共用JsonEngine的 JacksonObjectMapper(含withExactBigDecimals(true)的数值精度设置)与 Jayway JSONPathALWAYS_RETURN_LIST配置,格式化行为严格受这两个底层配置约束,调整它们会同时影响两条链路。
  • 桌面应用
  • 开发工具

【免费下载链接】MooTool

Handy tool set for developers

项目地址:https://gitcode.com/gh_mirrors/mo/MooTool
点击查看免费下载
上一篇:SpiderFoot OSINT信息收集实战:15分钟完成首次扫描
下一篇:gh_mirrors/awe/awesome-angular2项目国际化实践:面向全球用户的应用开发

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询