HarmonyKit | JSON 格式化工具:从三行核心算法到完整交互设计 HarmonyKit | JSON 格式化工具从三行核心算法到完整交互设计引言为什么 JSON 格式化排在第一个打开 HarmonyKit 的首页五个工具分类中排在最前面的是格式化其中第一个工具是JSON 格式化。这不是随机排列——JSON 格式化是开发者移动端最高频的需求。线上排查问题时复制一段 JSON 日志需要格式化查看结构书写 API 文档时需要把压缩的 JSON 展开对接第三方接口时需要验证返回的 JSON 是否合法。HarmonyKit 的 JSON 格式化工具看似简单——三行核心算法代码——但它背后涉及的类型约束、错误处理哲学、缩进切换设计和格式化/压缩双模式构成了一个完整的产品设计案例。这篇文章将详细拆解 JSON 格式化工具的全部实现细节。项目仓库https://atomgit.com/VON-/harmony-kit核心算法三行代码两层考量JsonUtils 的核心逻辑文件仅有 27 行包含三个静态方法interfaceValidateResult{valid:boolean;error:string;}exportclassJsonUtils{staticformat(input:string,indent:number2):string{constobj:ObjectJSON.parse(input);returnJSON.stringify(objasobject,null,indent);}staticcompress(input:string):string{constobj:ObjectJSON.parse(input);returnJSON.stringify(objasobject);}staticvalidate(input:string):ValidateResult{try{JSON.parse(input);constresult:ValidateResult{valid:true,error:};returnresult;}catch(e){constresult:ValidateResult{valid:false,error:(easError).message};returnresult;}}}三行核心代码拆开后每行都有两个层面的考量第一层功能正确性。JSON.parse(input)将字符串解析为 JavaScript 对象。JSON.stringify(obj, null, indent)将对象重新序列化为带缩进的字符串——null是 replacer不进行属性过滤indent是缩进空格数。第二层ArkTS 类型约束。const obj: Object JSON.parse(input)中的Object不能省略——ArkTS 禁止隐式类型推导。obj as object不能省略——Object类型包含 string/number不能直接传参给JSON.stringify()必须先断言为object。const result: ValidateResult {...}不能省略——ArkTS 禁止未标注类型的对象字面量。这反映了 HarmonyKit 中所有工具类的一个共同特征核心逻辑可能只有两三个 API 调用但为了让这些调用在 ArkTS 的类型系统中合法运行需要额外的类型标注和断言。这些标注不是累赘——它们让每一次类型转换都在编译期被验证消除了运行时隐式类型转换的风险。JSON.stringify 的三个参数详解JSON.stringify()在 ArkTS 中的完整签名是JSON.stringify(value: object, replacer?: (key: string, value: Object) Object | null, space?: string | number): string第一个参数 value要序列化的对象。类型必须是object不能是string、number等基本类型这就是为什么需要obj as object断言。第二个参数 replacer可选。一个过滤函数或属性名数组用于控制哪些属性被包含在输出中。在 HarmonyKit 中传入null表示不进行属性过滤——所有可枚举属性都会被序列化。第三个参数 space可选。控制输出的缩进。传入number时表示缩进空格数最大值 10传入string时表示使用该字符串作为缩进前缀如\t表示 Tab 缩进。在 HarmonyKit 中传入indent2 或 4这是最常见的两种缩进风格。压缩模式实际上就是省略第三个参数——JSON.stringify(obj)等价于JSON.stringify(obj, null, 0)仅当第三个参数是数字且为 0 时。但传入undefined或null作为第三个参数表示不格式化输出完全不带缩进和换行// 格式化JSON.stringify({a:1},null,2)// {\n a: 1\n}// 压缩JSON.stringify({a:1})// {a:1}错误处理定位信息比错误本身更重要JSON 格式化的错误处理是 HarmonyKit 中最仔细设计的交互之一。JSON.parse()抛出的SyntaxError包含错误发生的位置信息SyntaxError: Unexpected token } in JSON at position 42这个错误信息中的position 42是最有价值的部分——它告诉用户 JSON 的第 42 个字符处有问题。HarmonyKit 不做任何信息过滤直接将这个错误原封不动地展示给用户// 在 JsonFormatter 页面中if(this.errorMsg){Text(this.errorMsg).fontSize(12).fontColor(#ff3b30).width(100%).padding({left:16,right:16,top:4,bottom:4});}错误信息以红色小字展示在格式化结果区域的上方。#ff3b30是 iOS 的系统红System Red选择这个颜色而不是鲜艳的红是因为它和 HarmonyKit 的整体灰白配色更兼容。validate方法的职责分离值得注意。它只做验证并返回结果不修改任何状态不设置output不修改input。format和compress方法也不重复做验证——它们信任调用方已经通过validate验证了输入的合法性。这种职责分离让每个方法的目的单一明确validate告诉你能不能格式化format执行格式化compress执行压缩。三个方法相互独立没有任何隐藏的依赖关系。缩进切换2 空格 vs 4 空格JSON 格式化工具的缩进切换看似简单两个按钮二元选择但背后考虑了一个实际的产品问题不同技术栈的缩进习惯不同。前端开发者React、Vue、Angular通常使用 2 空格缩进——因为 JSX/模板语法的嵌套层级深2 空格在水平方向上更紧凑。后端开发者Java、Go、Python通常使用 4 空格缩进——符合各自语言社区的代码规范。一些开发者可能使用 Tab 缩进。两个按钮的选择覆盖了约 90% 的开发者偏好。实现方式是StateindentSize:number2;// 2空格按钮Button(2空格).fontSize(12).height(32).backgroundColor(this.indentSize2?#007aff:#f0f0f0).fontColor(this.indentSize2?#ffffff:#333333).borderRadius(6).onClick((){this.indentSize2;});// 4空格按钮Button(4空格).fontSize(12).height(32).backgroundColor(this.indentSize4?#007aff:#f0f0f0).fontColor(this.indentSize4?#ffffff:#333333).borderRadius(6).margin({left:8}).onClick((){this.indentSize4;});选中的按钮显示蓝色填充#007aff和白色文字未选中的按钮显示灰色背景#f0f0f0和深灰文字#333333。视觉上选中的按钮是激活态未选中的按钮是待选态。这种二元开关不用 Toggle 组件的原因是Toggle 需要一个标签来描述它控制什么而两个按钮本身就是标签——2空格和4空格是自描述的不需要额外的标签文字。UI 层面的一个细节两个按钮之间用margin({ left: 8 })分隔而不是用 Row 的space属性统一管理。原因是这两个按钮是互斥选项组——它们之间的间距应该与其他按钮格式化、压缩不同后者需要更大的视觉分离度。格式化 vs 压缩两个按钮的哲学从技术上讲格式化和压缩是同一个操作的两个参数化变体// 格式化staticformat(input:string,indent:number2):string{constobj:ObjectJSON.parse(input);returnJSON.stringify(objasobject,null,indent);}// 压缩staticcompress(input:string):string{constobj:ObjectJSON.parse(input);returnJSON.stringify(objasobject);}format调用JSON.stringify(obj, null, indent)compress调用JSON.stringify(obj, null, undefined)第三个参数为 undefined 表示不格式化。两者的区别仅在于第三个参数。但 UI 上将它们设计为两个独立的按钮而不是一个缩进0 就是压缩的滑块/输入框有一个产品层面的考量降低用户的理解成本。如果只有一个缩进空格数输入框用户需要理解把缩进设为 0 就是压缩。这个映射对开发者来说简单但对试用应用的新用户来说需要思考。两个独立按钮将格式化和压缩作为两个独立操作呈现——两个操作都是带明确标签的按钮不需要用户做任何思维映射。按钮的颜色也反映了它们的角色“格式化按钮用蓝色#007aff——这是正向操作的颜色暗示这就是你要找的功能”压缩按钮用绿色#34c759——这是辅助操作的颜色区分于主操作输入输出的交互流程JsonFormatter 页面的完整交互流程如下用户在顶部 TextArea 中输入或粘贴 JSON 字符串onChange回调更新this.input同时清空this.errorMsg输入的变更意味着之前的错误可能已被修复用户点击格式化按钮 →formatJson()方法被调用formatJson()首先检查输入是否为空 → 如果空设置错误信息并返回然后调用JsonUtils.validate()验证 JSON 合法性 → 如果不合法设置错误信息并返回验证通过后调用JsonUtils.format()执行格式化 → 设置this.output输出结果显示在下方的 TextArea 中用户在输出结果旁边的 CopyButton 上点击 → 结果被复制到系统剪贴板formatJson(){if(!this.input.trim()){this.errorMsg请输入 JSON 字符串;return;}letresultJsonUtils.validate(this.input);if(!result.valid){this.errorMsgresult.error;this.output;return;}this.errorMsg;this.outputJsonUtils.format(this.input,this.indentSize);}这个流程的每一步都有提前返回early return的 guard clause——空输入、验证失败的 guard 都在执行格式化之前拦截并返回。GGuard clause 是 HarmonyKit 中所有工具方法的标准模式——先检查所有不该发生的情况快速退出最后才是正常情况下的执行逻辑。这种模式让代码的阅读路径从成功逻辑中嵌入错误处理变为先处理所有错误最后执行成功逻辑——代码的嵌套层级更浅更容易理解。compressJson()方法的流程完全相同只是最后调用的是JsonUtils.compress()而不是JsonUtils.format()。工具页的一致化设计JsonFormatter 页面遵循了 HarmonyKit 所有工具页的统一布局模板Column全屏容器 ├── Row标题栏返回按钮 页面标题 └── Scroll可滚动内容区 └── Column ├── Row输入标签 CopyButton ├── TextArea输入框 ├── Row操作按钮组 ├── [错误信息 Text]条件渲染 ├── Row输出标签 CopyButton └── TextArea输出框这个模板在 10 个工具页中保持一致。唯一的变量是操作按钮区域的内容ToolBar 模式 vs 单个按钮 vs 两个按钮 vs 带选项的按钮组输入框的类型单行 TextInput vs 多行 TextArea是否有额外的辅助控件缩进选择、算法选择、flag 切换等。标题栏的返回按钮使用了系统图标Button({type:ButtonType.Circle}){Image($r(sys.media.ohos_ic_public_arrow_left)).width(24).height(24).fillColor(#333333)}.width(32).height(32).backgroundColor(Color.Transparent).onClick(()this.getUIContext().getRouter().back());$r(sys.media.ohos_ic_public_arrow_left)是鸿蒙系统内置的返回箭头图标。sys.media前缀表明这是系统资源而非应用资源。使用系统图标的好处是用户看到的是一个熟悉的返回箭头——所有鸿蒙应用都用同一个图标一致性体验。为什么用 TextArea 而不是 TextInputJSON 格式化工具的输入和输出都使用了TextArea多行文本输入框而不是TextInput单行输入框。原因是 JSON 字符串本质上是一个多行文本块——即使压缩后的 JSON 只有一行格式化后的 JSON 一定包含多行。TextArea的配置参数TextArea({text:this.input,placeholder:粘贴 JSON 字符串...}).height(160).fontSize(13).fontFamily(monospace).backgroundColor(#ffffff).borderRadius(8).padding(12).margin({left:16,right:16}).onChange((value:string){this.inputvalue;this.errorMsg;});几个值得注意的样式选择fontFamily(monospace)等宽字体保证了 JSON 的结构缩进在水平方向对齐。如果使用默认的比例字体层级缩进会出现视觉上的阶梯——因为不同字符的宽度不同fontSize(13)比正文字体通常 15-16px略小在有限的屏幕空间内显示更多内容。13px 是移动端等宽字体的一个经验值——12px 太紧凑14px 浪费空间backgroundColor(#ffffff)白色背景区分于页面的浅灰背景#f5f5f5明确标识这是交互区域borderRadius(8)适中的圆角让输入框看起来友好但不幼稚padding(12)内容区域与边框之间留出足够间距文字不会贴边性能考量JSON 格式化工具的性能瓶颈只有一个JSON.stringify()处理超大 JSON 时的内存占用。一个 10MB 的 JSON 字符串JSON.parse()会创建一个约 15-20MB 的对象JavaScript 对象的内存开销通常大于原始字符串JSON.stringify()再创建一个约 10MB 的输出字符串。总计约 25-30MB 的临时内存分配。对于移动端来说30MB 的内存在一部 8GB RAM 的手机上不成问题。但如果用户连续处理多个超大 JSON比如从 50MB 的日志文件中反复复制粘贴片段内存占用会累积。HarmonyKit 没有做内存优化没有使用流式解析、没有主动触发 GC因为开发者工具箱的真实使用场景中JSON 片段通常不超过几百 KB——一段 API 响应、一个配置文件、一小段日志。总结JSON 格式化工具是 HarmonyKit 中最简单的工具——三行核心逻辑。但它的实现是完整的验证先行的安全策略、错误信息的透明展示、缩进切换的用户选择、格式化/压缩的双操作设计、系统图标的一致性、等宽字体的排版保障。这些细节单个看都很微小但堆叠在一起构成了从功能可用的 JSON 格式化到体验完备的 JSON 格式化工具之间的差距。对于 ArkUI 开发者来说JSON 格式化工具也展示了如何在严格的类型系统下正确使用JSON.parse()和JSON.stringify()——通过Object类型标注、as object断言、ValidateResultinterface 提取让标准库 API 在 ArkTS 的类型约束下合规运行。项目仓库https://atomgit.com/VON-/harmony-kit