KMP 实现鸿蒙跨端:Kotlin JSON 美化和分析器
本文介绍了一个基于Kotlin Multiplatform(KMP)的JSON处理工具,能够跨平台运行于鸿蒙应用。该工具提供JSON验证、格式化、结构分析和错误检测等功能,核心实现包括: 括号匹配检查 - 使用栈结构验证{}和[]的正确嵌套 引号匹配检查 - 追踪转义字符确保引号正确闭合 结构统计分析 - 统计对象、数组、键值对及各种值类型的数量 JSON格式化 - 实现自动缩进和换行美化 该工具

目录
概述
本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个完整的 JSON 美化和分析系统。这个案例展示了如何使用 Kotlin 的字符串处理、状态机和数据分析来创建一个功能丰富的 JSON 工具。通过 KMP,这个工具可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。
工具的特点
- JSON 验证:检查 JSON 的合法性
- 格式化:美化 JSON 使其易于阅读
- 结构分析:统计 JSON 的结构信息
- 错误检测:识别常见的 JSON 错误
- 大小分析:计算原始和格式化后的大小
- 跨端兼容:一份 Kotlin 代码可同时服务多个平台
工具功能
1. JSON 验证
- 括号匹配:检查 {} 和 [] 是否匹配
- 引号匹配:检查 “” 是否匹配
- 转义处理:正确处理转义字符
- 错误报告:提示具体的错误信息
2. 结构分析
- 对象统计:计算 {} 的个数
- 数组统计:计算 [] 的个数
- 键值对统计:计算 : 的个数
- 值类型统计:统计字符串、数字、布尔值、空值的个数
3. 格式化
- 缩进处理:使用 2 空格缩进
- 换行处理:在合适的位置换行
- 空格处理:移除不必要的空格
4. 大小分析
- 原始大小:输入 JSON 的字节数
- 格式化大小:格式化后的字节数
- 增长率:计算大小的变化百分比
核心实现
1. 括号匹配检查
fun checkBrackets(): Boolean {
val stack = mutableListOf<Char>()
var inString = false
var escapeNext = false
for (char in json) {
if (escapeNext) {
escapeNext = false
continue
}
if (char == '\\') {
escapeNext = true
continue
}
if (char == '"') {
inString = !inString
continue
}
if (inString) continue
when (char) {
'{', '[' -> stack.add(char)
'}' -> {
if (stack.isEmpty() || stack.last() != '{') return false
stack.removeAt(stack.size - 1)
}
']' -> {
if (stack.isEmpty() || stack.last() != '[') return false
stack.removeAt(stack.size - 1)
}
}
}
return stack.isEmpty()
}
代码说明:
这个函数使用栈数据结构来验证 JSON 中的括号是否正确匹配。函数首先初始化一个栈、两个标志位(inString 追踪是否在字符串内,escapeNext 追踪是否需要跳过下一个字符)。遍历 JSON 字符串中的每个字符,首先处理转义字符的逻辑:如果 escapeNext 为真,说明当前字符是被转义的,直接跳过。如果当前字符是反斜杠,设置 escapeNext 为真。然后处理引号,切换 inString 标志。如果当前在字符串内,跳过所有字符。对于不在字符串内的字符,使用 when 表达式处理:遇到开放括号时压入栈,遇到关闭括号时检查栈顶是否为对应的开放括号,如果是则弹出,否则返回 false。最后检查栈是否为空,为空则所有括号都匹配。
2. 引号匹配检查
fun checkQuotes(): Boolean {
var inString = false
var escapeNext = false
for (char in json) {
if (escapeNext) {
escapeNext = false
continue
}
if (char == '\\') {
escapeNext = true
continue
}
if (char == '"') {
inString = !inString
}
}
return !inString
}
代码说明:
这个函数验证 JSON 中的引号是否正确匹配。使用 inString 标志追踪当前是否在字符串内,escapeNext 标志追踪是否需要跳过下一个字符。遍历 JSON 字符串中的每个字符,首先处理转义字符:如果 escapeNext 为真,直接跳过当前字符并重置标志。如果当前字符是反斜杠,设置 escapeNext 为真。如果当前字符是引号且不是被转义的,切换 inString 标志。最后检查 inString 是否为假,为假则所有引号都正确匹配。这个函数比括号匹配检查更简单,因为 JSON 中只有一种引号。
3. 结构统计
var braceCount = 0
var bracketCount = 0
var colonCount = 0
var stringCount = 0
var numberCount = 0
var boolCount = 0
var nullCount = 0
var inString = false
var escapeNext = false
var i = 0
while (i < json.length) {
val char = json[i]
// 处理转义字符
if (escapeNext) {
escapeNext = false
i++
continue
}
if (char == '\\') {
escapeNext = true
i++
continue
}
if (char == '"') {
inString = !inString
if (inString) stringCount++
i++
continue
}
if (inString) {
i++
continue
}
when (char) {
'{' -> braceCount++
'[' -> bracketCount++
':' -> colonCount++
't', 'f' -> {
if (json.substring(i).startsWith("true") || json.substring(i).startsWith("false")) {
boolCount++
i += if (json.substring(i).startsWith("true")) 3 else 4
}
}
'n' -> {
if (json.substring(i).startsWith("null")) {
nullCount++
i += 3
}
}
in '0'..'9', '-' -> {
if (i == 0 || json[i - 1] in setOf(':', '[', ',', ' ')) {
numberCount++
while (i < json.length && (json[i].isDigit() || json[i] in setOf('.', 'e', 'E', '+', '-'))) {
i++
}
i--
}
}
}
i++
}
代码说明:
这段代码统计 JSON 中各种元素的数量。初始化多个计数器变量来追踪不同类型的元素。使用 while 循环遍历 JSON 字符串,首先处理转义字符的逻辑。然后处理引号,切换 inString 标志,当进入字符串时增加 stringCount。如果当前在字符串内,跳过该字符。对于不在字符串内的字符,使用 when 表达式进行统计:遇到 { 时增加 braceCount,遇到 [ 时增加 bracketCount,遇到 : 时增加 colonCount。对于布尔值,检查是否以 “true” 或 “false” 开头,如果是则增加 boolCount 并跳过相应的字符数。对于 null 值,检查是否以 “null” 开头,如果是则增加 nullCount 并跳过 3 个字符。对于数字,检查前一个字符是否为有效的分隔符,如果是则增加 numberCount 并跳过整个数字。
4. JSON 格式化
val formatted = mutableListOf<String>()
var indent = 0
var inString = false
var escapeNext = false
var currentLine = ""
for (char in json) {
if (escapeNext) {
currentLine += char
escapeNext = false
continue
}
if (char == '\\') {
currentLine += char
escapeNext = true
continue
}
if (char == '"') {
inString = !inString
currentLine += char
continue
}
if (inString) {
currentLine += char
continue
}
when (char) {
'{', '[' -> {
currentLine += char
formatted.add(" ".repeat(indent) + currentLine)
currentLine = ""
indent++
}
'}', ']' -> {
if (currentLine.isNotEmpty()) {
formatted.add(" ".repeat(indent) + currentLine)
currentLine = ""
}
indent--
formatted.add(" ".repeat(indent) + char)
}
',' -> {
currentLine += char
formatted.add(" ".repeat(indent) + currentLine)
currentLine = ""
}
':' -> {
currentLine += char + " "
}
' ' -> {
// 忽略空格
}
else -> {
currentLine += char
}
}
}
代码说明:
这段代码实现了 JSON 的格式化功能。初始化一个列表来存储格式化后的行,indent 变量追踪当前的缩进级别,inString 和 escapeNext 标志用于处理字符串和转义字符,currentLine 用于构建当前行的内容。遍历 JSON 字符串中的每个字符,首先处理转义字符和引号的逻辑。如果当前在字符串内,直接添加字符到 currentLine。对于不在字符串内的字符,使用 when 表达式处理:遇到开放括号时,添加当前行到列表,增加缩进级别。遇到关闭括号时,先添加当前行(如果非空),减少缩进级别,然后添加关闭括号。遇到逗号时,添加当前行到列表。遇到冒号时,添加冒号和一个空格。忽略所有空格字符。其他字符直接添加到 currentLine。这样可以生成格式化的 JSON,每行都有适当的缩进。
实战案例
案例:完整的 JSON 美化和分析器
Kotlin 源代码
代码说明:
这是 JSON 美化和分析器的完整 Kotlin 实现,使用 @JsExport 装饰器将其导出为 JavaScript 可调用的函数。函数接收一个 JSON 字符串参数,默认值为一个示例 JSON 对象。函数执行多个步骤:首先进行输入验证,检查 JSON 是否为空。然后验证括号和引号的匹配情况。接着统计 JSON 的结构信息,包括对象数量、数组数量、字符串数量、数字数量等。最后进行格式化处理,将紧凑的 JSON 转换为易读的格式。函数返回一个详细的分析报告,包含验证结果、结构统计和格式化后的 JSON。
@OptIn(ExperimentalJsExport::class)
@JsExport
fun jsonBeautifier(inputJson: String = "{\"name\":\"John\",\"age\":30}"): String {
val json = inputJson.trim()
if (json.isEmpty()) {
return "❌ 错误: JSON 不能为空"
}
// 1. 验证括号匹配
fun checkBrackets(): Boolean {
val stack = mutableListOf<Char>()
var inString = false
var escapeNext = false
for (char in json) {
if (escapeNext) {
escapeNext = false
continue
}
if (char == '\\') {
escapeNext = true
continue
}
if (char == '"') {
inString = !inString
continue
}
if (inString) continue
when (char) {
'{', '[' -> stack.add(char)
'}' -> {
if (stack.isEmpty() || stack.last() != '{') return false
stack.removeAt(stack.size - 1)
}
']' -> {
if (stack.isEmpty() || stack.last() != '[') return false
stack.removeAt(stack.size - 1)
}
}
}
return stack.isEmpty()
}
var isValid = checkBrackets()
// 2. 统计结构
var braceCount = 0
var bracketCount = 0
var stringCount = 0
var numberCount = 0
// ... 统计逻辑
// 3. 格式化
val formatted = mutableListOf<String>()
// ... 格式化逻辑
val formattedJson = formatted.joinToString("\n")
return "🔧 JSON 美化和分析器\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"1️⃣ 验证结果:\n" +
" 状态: ${if (isValid) "✅ 有效" else "❌ 无效"}\n\n" +
"2️⃣ 结构统计:\n" +
" 对象数: $braceCount\n" +
" 数组数: $bracketCount\n" +
" 字符串: $stringCount\n" +
" 数字: $numberCount\n\n" +
"4️⃣ 格式化结果:\n" +
formattedJson + "\n\n" +
"✅ 处理完成!"
}
ArkTS 调用代码
代码说明:
这是 OpenHarmony ArkTS 页面的完整实现,展示了如何集成和调用 Kotlin 编译生成的 JSON 美化函数。首先通过 import 语句从 ./hellokjs 模块导入 jsonBeautifier 函数。页面使用 @Entry 和 @Component 装饰器定义为可入口的组件。定义了四个响应式状态变量:message 显示操作状态,results 存储分析结果,caseTitle 显示标题,inputText 存储用户输入的 JSON。loadResults() 方法调用 Kotlin 函数进行美化和分析,并将结果存储在 results 数组中。build() 方法定义了完整的 UI 布局,包括标题、状态信息、输入框、美化按钮、结果展示区域等,使用了 Column、Text、TextInput、Button 等组件构建了一个功能完整的用户界面。
import { jsonBeautifier } from './hellokjs';
@Entry
@Component
struct Index {
@State message: string = '加载中...';
@State results: string[] = [];
@State caseTitle: string = 'JSON 美化和分析器';
@State inputText: string = '{"name":"John","age":30}';
loadResults(): void {
try {
const algorithmResult: string = jsonBeautifier(this.inputText);
this.results = [algorithmResult];
this.message = '✓ 分析完成';
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.message = `✗ 错误: ${errorMessage}`;
}
}
build() {
Column() {
// ... UI 代码
}
}
}
编译过程详解
Kotlin 到 JavaScript 的转换
| Kotlin 特性 | JavaScript 等价物 |
|---|---|
| mutableListOf | 数组 [] |
| String.substring | 字符串 slice |
| startsWith | 字符串 startsWith |
| repeat | 字符串 repeat |
| joinToString | 数组 join |
关键转换点
- 字符串处理:转换为 JavaScript 字符串方法
- 集合操作:转换为数组操作
- 状态机:转换为循环和条件判断
- 缩进处理:转换为字符串重复
工具扩展
扩展 1:添加 JSON 压缩
fun compressJson(): String {
var result = ""
var inString = false
var escapeNext = false
for (char in json) {
if (escapeNext) {
result += char
escapeNext = false
continue
}
if (char == '\\') {
result += char
escapeNext = true
continue
}
if (char == '"') {
inString = !inString
result += char
continue
}
if (inString) {
result += char
} else if (!char.isWhitespace()) {
result += char
}
}
return result
}
代码说明:
这个函数实现了 JSON 的压缩功能,移除所有不必要的空白字符以减小文件大小。初始化结果字符串和两个标志位来追踪转义字符和字符串状态。遍历 JSON 字符串中的每个字符,首先处理转义字符的逻辑。然后处理引号,切换 inString 标志。如果当前在字符串内,直接添加字符到结果。如果不在字符串内且当前字符不是空白字符,则添加到结果。这样可以移除 JSON 中所有不必要的空白字符(如缩进、换行等),同时保留字符串内的空白字符。压缩后的 JSON 通常可以减少 30-50% 的大小。
扩展 2:添加 JSON 路径查询
fun getValueByPath(path: String): String {
// 实现 JSONPath 查询
// 例如: $.name, $.hobbies[0]
return ""
}
代码说明:
这个函数展示了如何实现 JSONPath 查询功能,允许用户通过路径表达式来访问 JSON 中的特定值。函数接收一个路径字符串作为参数,例如 $.name 表示访问根对象的 name 属性,$.hobbies[0] 表示访问根对象的 hobbies 数组的第一个元素。实现 JSONPath 需要解析路径表达式,然后根据表达式逐步导航到 JSON 中的目标位置。这个函数的完整实现会比较复杂,需要处理对象属性访问、数组索引访问、递归查询等多种情况。
扩展 3:添加 JSON 转换
fun jsonToMap(): Map<String, Any> {
// 将 JSON 转换为 Kotlin Map
return mapOf()
}
代码说明:
这个函数展示了如何将 JSON 对象转换为 Kotlin 的 Map 数据结构。函数返回一个 Map<String, Any> 类型,其中键是 JSON 对象的属性名,值是对应的属性值。实现这个函数需要解析 JSON 字符串,识别所有的键值对,然后将其转换为 Map。这个函数的完整实现需要处理多种数据类型(字符串、数字、布尔值、null、对象、数组等),以及嵌套的 JSON 结构。转换后的 Map 可以在 Kotlin 代码中更方便地访问和操作 JSON 数据。
扩展 4:添加 JSON 比较
fun compareJson(other: String): String {
// 比较两个 JSON 的差异
return ""
}
代码说明:
这个函数展示了如何比较两个 JSON 对象的差异。函数接收另一个 JSON 字符串作为参数,然后比较当前 JSON 和另一个 JSON 之间的差异。实现这个函数需要解析两个 JSON 字符串,然后逐个比较它们的结构和内容。可能的差异包括:缺失的属性、额外的属性、属性值不同、数据类型不同等。函数可以返回一个详细的差异报告,列出所有的差异项。这个功能在版本控制、数据同步、配置文件比较等场景中非常有用。
最佳实践
1. 使用状态机处理字符串
// ✅ 好:使用状态机
var inString = false
var escapeNext = false
for (char in json) {
if (escapeNext) {
escapeNext = false
continue
}
if (char == '\\') {
escapeNext = true
continue
}
if (char == '"') {
inString = !inString
}
}
// ❌ 不好:简单的字符查找
val inString = json.contains('"')
代码说明:
这个示例对比了两种处理 JSON 字符串的方法。第一种方法使用状态机,通过 inString 和 escapeNext 标志来追踪当前的状态,能够正确处理转义字符和字符串边界。这种方法虽然代码更复杂,但能够准确地识别字符串内的内容,避免误处理字符串内的特殊字符。第二种方法使用简单的 contains() 检查,虽然代码简洁,但无法区分字符串内和字符串外的引号,容易导致错误的结果。最佳实践是在处理结构化文本时使用状态机来准确追踪状态。
2. 使用栈验证括号
// ✅ 好:使用栈
val stack = mutableListOf<Char>()
for (char in json) {
when (char) {
'{', '[' -> stack.add(char)
'}' -> if (stack.last() == '{') stack.removeAt(stack.size - 1)
']' -> if (stack.last() == '[') stack.removeAt(stack.size - 1)
}
}
// ❌ 不好:计数器
var braceCount = 0
for (char in json) {
if (char == '{') braceCount++
if (char == '}') braceCount--
}
代码说明:
这个示例对比了两种验证括号匹配的方法。第一种方法使用栈数据结构,能够准确验证括号的嵌套关系。当遇到开放括号时压入栈,遇到关闭括号时检查栈顶是否为对应的开放括号。这种方法可以检测括号是否正确嵌套,例如 {[]} 是有效的,但 {[}] 是无效的。第二种方法使用简单的计数器,只能检查括号的数量是否相等,无法检测嵌套关系。例如,{[}] 会被认为是有效的,但实际上是无效的。最佳实践是使用栈来验证括号的正确匹配和嵌套。
3. 提前验证输入
// ✅ 好:提前检查
if (json.isEmpty()) {
return "错误: JSON 不能为空"
}
// ❌ 不好:处理过程中才发现
try {
// 处理 JSON
} catch (e: Exception) {
return "错误"
}
代码说明:
这个示例对比了两种处理输入验证的方法。第一种方法在处理 JSON 之前进行提前验证,检查输入是否为空。这样可以及早发现问题,避免后续的复杂处理,提高代码的效率和可读性。第二种方法依赖于 try-catch 块来捕获处理过程中的异常,虽然也能处理错误,但效率较低,且错误信息可能不够具体。最佳实践是在处理数据之前进行全面的输入验证,包括检查空值、格式、范围等,以提高代码的健壮性和性能。
常见问题
Q1: 如何处理转义字符?
A: 使用 escapeNext 标志追踪转义状态:
var escapeNext = false
for (char in json) {
if (escapeNext) {
escapeNext = false
continue
}
if (char == '\\') {
escapeNext = true
}
}
Q2: 如何正确计算 JSON 大小?
A: 使用字符串长度:
val originalSize = json.length
val formattedSize = formattedJson.length
Q3: 如何处理 Unicode 字符?
A: Kotlin 原生支持 Unicode,直接处理即可。
Q4: 如何验证 JSON 的有效性?
A: 检查括号匹配和引号匹配,以及值的类型。
Q5: 如何优化大型 JSON 的处理?
A: 使用流式处理而不是一次性加载到内存。
总结
关键要点
- ✅ 使用状态机处理字符串
- ✅ 使用栈验证括号匹配
- ✅ 正确处理转义字符
- ✅ 提前验证输入
- ✅ KMP 能无缝编译到 JavaScript
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)