在这里插入图片描述

JSON 格式化算是我在“工具箱类 App”里最早加的一个功能:需求简单,但能把输入校验、状态管理、交互细节都串起来。

这个小工具我一般会做到这几件事:

  • [美化] 把一行 JSON 按缩进排版,方便阅读
  • [压缩] 把带缩进/换行的 JSON 压成一行,方便粘贴到日志或接口参数
  • [校验] 输入不合法时给出明确提示(别让用户猜哪里错了)
  • [便捷操作] 一键复制结果、一键清空输入

JSON格式化工具的应用

JSON格式化在API开发和数据处理中非常重要。开发者经常需要验证JSON数据的有效性,或者将压缩的JSON数据进行美化以便阅读。

在工具页面中,我是把 JSON 格式化当成一个普通的工具项挂到列表里。这里不建议把配置写得太复杂,能读懂、能扩展就行。

{'icon': Icons.data_object, 'title': 'JSON格式化'},

说明

这里的关键点其实不是图标和标题,而是你后面“点了这一项要做什么”。我习惯再配一个 onTaptoolKey,避免后期靠 title 字符串去判断(容易踩坑:改文案就把逻辑改炸了)。

JSON格式化对话框的实现

用户点击“JSON 格式化”后,我这里用对话框承载交互,原因很现实:

  • [场景短] 格式化通常是一次性操作,不需要专门开一个页面
  • [实现快] 复用现有页面结构,少改路由

入口方法我会尽量写薄一点,只负责打开弹窗,把状态交给一个独立组件。

void _showJsonFormatterDialog(BuildContext context) {
  showDialog(
    context: context,
    builder: (_) => const _JsonFormatterDialog(),
  );
}

说明

showDialog 里如果塞太多 UI,很快就会变成“超长方法”。我这里把真实交互抽成 _JsonFormatterDialog,后面要加“复制”“清空”“模式切换”都不会把入口方法越写越胖。

接下来是弹窗主体。这个组件需要维护输入/输出文本、当前模式,以及按钮点击后的状态刷新,所以我用 StatefulWidget

class _JsonFormatterDialog extends StatefulWidget {
  const _JsonFormatterDialog();

  
  State<_JsonFormatterDialog> createState() => _JsonFormatterDialogState();
}

enum _JsonMode { pretty, compact }

说明

  • [模式枚举]_JsonMode 比用 bool isPretty 更直观,后面如果加“排序 key”“保留 unicode”这种开关也好扩展。
  • [私有组件] 前面加下划线是为了把实现细节限定在当前文件里,减少全局污染(工具页面越做越多时很有用)。

状态类里我会把 TextEditingController 放在一起管理,并且记得释放(这个点在工具类弹窗里很容易被忽略)。

class _JsonFormatterDialogState extends State<_JsonFormatterDialog> {
  final _inputCtrl = TextEditingController();
  final _outputCtrl = TextEditingController();
  _JsonMode _mode = _JsonMode.pretty;

  
  void dispose() {
    _inputCtrl.dispose();
    _outputCtrl.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('JSON格式化'),
      content: _buildContent(context),
      actions: _buildActions(context),
    );
  }
}

说明

  • [控制器] 输入/输出各一个 controller,输出框设为只读,避免用户误改。
  • [释放资源] 这种弹窗经常被频繁打开,dispose() 不做的话 controller 会一直挂着,时间久了内存和监听堆积很明显。

内容区我会按“模式切换 + 输入 + 输出”的顺序排,保证用户一眼知道先做什么。

Widget _buildContent(BuildContext context) {
  return SingleChildScrollView(
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        _buildModeSwitch(),
        SizedBox(height: 12.h),
        TextField(
          controller: _inputCtrl,
          maxLines: 6,
          decoration: InputDecoration(
            hintText: '输入JSON',
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(8.r),
            ),
          ),
        ),
        SizedBox(height: 12.h),
        TextField(
          controller: _outputCtrl,
          readOnly: true,
          maxLines: 6,
          decoration: InputDecoration(
            hintText: '结果输出',
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(8.r),
            ),
          ),
        ),
      ],
    ),
  );
}

说明

我把“输入”和“输出”都做成多行文本框,主要是适配两种情况:

  • [输入很长] 接口返回的 JSON 可能一大坨,必须能滚动
  • [输出要复制] 输出框用 controller,复制时直接读 _outputCtrl.text,不用到处传值

JSON解析和验证

JSON格式化的第一步是解析JSON数据,验证其有效性。在Dart中,我们可以使用jsonDecode函数来解析JSON字符串:

import 'dart:convert';

说明

这块我一般只引入 dart:convert,避免为了格式化 JSON 额外上三方包。工具类功能越多,依赖越少越稳。

解析和转换我会拆成一个动作方法 _run(),点“执行”时调用。失败不把异常直接怼到 UI 上,而是给一个更友好的提示。

void _run() {
  final input = _inputCtrl.text.trim();
  if (input.isEmpty) {
    _toast(context, '先粘贴一段 JSON 再执行');
    return;
  }

  try {
    final jsonData = jsonDecode(input);
    final result = _mode == _JsonMode.pretty
        ? const JsonEncoder.withIndent('  ').convert(jsonData)
        : jsonEncode(jsonData);

    setState(() {
      _outputCtrl.text = result;
    });
  } catch (_) {
    _toast(context, 'JSON 解析失败:请检查引号、逗号和括号是否匹配');
  }
}

说明

  • [trim] 用户从日志里复制内容时,经常会带换行/空格,先 trim() 能少一些“看不懂的错误”。
  • [异常信息] 生产工具里我通常不会直接展示 $e,一是信息太长,二是对用户帮助有限;更实用的是告诉他“先检查哪些点”。

JSON美化的实现

JSON美化是指将压缩的JSON数据进行格式化,添加缩进和换行符,使其更易于阅读。JsonEncoder.withIndent方法可以指定缩进的字符串。

说明

JsonEncoder.withIndent(' ') 这里我固定用两个空格,是因为移动端屏幕小,四个空格会把层级挤得很难看。如果你希望更紧凑,可以改成一个空格,甚至换成 \t

JSON压缩的实现

与美化相反,JSON压缩是指将格式化的JSON数据进行压缩,移除所有不必要的空格和换行符。

说明

压缩我没有再单独写 compressJson(),而是合并进 _run():同一份输入,按 _mode 输出不同结果。这样入口更少,用户也不需要理解“两个按钮两套逻辑”。

双向转换的支持

JSON格式化工具应该支持美化和压缩两种模式。用户可以通过选择不同的模式来切换功能。

Widget _buildModeSwitch() {
  return Row(
    children: [
      Expanded(
        child: SegmentedButton<_JsonMode>(
          segments: const [
            ButtonSegment(value: _JsonMode.pretty, label: Text('美化')),
            ButtonSegment(value: _JsonMode.compact, label: Text('压缩')),
          ],
          selected: {_mode},
          onSelectionChanged: (s) {
            setState(() => _mode = s.first);
          },
        ),
      ),
    ],
  );
}

说明

  • [交互直觉] 两段式切换比下拉框更像“工具开关”,用户不需要多思考。
  • [状态联动] 我这里切换模式不自动执行,是刻意的:有些人只想先切换再粘贴内容,自动执行反而会打断输入。

按钮区我会把“执行 / 复制 / 清空 / 关闭”这几个动作放齐,是真正用起来最顺手的组合。

List<Widget> _buildActions(BuildContext context) {
  return [
    TextButton(
      onPressed: () => Navigator.pop(context),
      child: const Text('关闭'),
    ),
    TextButton(
      onPressed: _clear,
      child: const Text('清空'),
    ),
    TextButton(
      onPressed: _copyResult,
      child: const Text('复制'),
    ),
    FilledButton(
      onPressed: _run,
      child: const Text('执行'),
    ),
  ];
}

说明

  • [执行] 我用 FilledButton 让主操作更突出,用户不会找半天。
  • [复制/清空] 工具类弹窗最常用的就是这俩;没有它们,体验会很“半成品”。

错误处理和提示

当用户输入无效的JSON数据时,系统应该显示清晰的错误消息,帮助用户快速定位问题。

void _toast(BuildContext context, String msg) {
  ScaffoldMessenger.of(context)
    ..hideCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text(msg)));
}

说明

我这里用 SnackBar 是因为它不挡住输入框,用户还能继续改 JSON。弹窗里用 AlertDialog 再套 AlertDialog 反而会让人烦躁。

清空和复制两个动作也顺手补一下实现,不然文章读到这里很难直接照搬落地。

void _clear() {
  setState(() {
    _inputCtrl.clear();
    _outputCtrl.clear();
  });
}

说明

清空我同时清输入和输出,避免“输出还是旧的”让用户误以为执行成功。

import 'package:flutter/services.dart';

void _copyResult() {
  final text = _outputCtrl.text;
  if (text.isEmpty) {
    _toast(context, '还没有结果可复制');
    return;
  }
  Clipboard.setData(ClipboardData(text: text));
  _toast(context, '已复制到剪贴板');
}

说明

  • [空结果提示] 这是很“人”的细节:用户点了复制没反应,会以为 App 卡了。
  • [不强依赖平台] Clipboard 这块在 OpenHarmony/多端场景里通常都能走 Flutter 的通用实现,兼容性比自己写平台通道省心。

总结

JSON 格式化这个功能看起来很小,但把它做“顺手”其实靠的是一堆细节:

  • [结构] 入口方法写薄,弹窗组件自己管状态
  • [体验] 美化/压缩切换清晰,复制/清空一键可用
  • [健壮] 空输入拦截、解析失败提示、controller 记得释放

后面如果你要继续扩展(比如 JSON 排序、转义/反转义、提取字段),也可以沿用这套思路:每个工具都尽量是一个独立的小组件,最后在工具列表里拼起来。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐