在这里插入图片描述

目录

  1. 概述
  2. 工具功能
  3. 核心实现
  4. 实战案例
  5. 编译过程详解
  6. 工具扩展
  7. 最佳实践
  8. 常见问题

概述

本文档介绍如何在 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 变量追踪当前的缩进级别,inStringescapeNext 标志用于处理字符串和转义字符,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

关键转换点

  1. 字符串处理:转换为 JavaScript 字符串方法
  2. 集合操作:转换为数组操作
  3. 状态机:转换为循环和条件判断
  4. 缩进处理:转换为字符串重复

工具扩展

扩展 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 字符串的方法。第一种方法使用状态机,通过 inStringescapeNext 标志来追踪当前的状态,能够正确处理转义字符和字符串边界。这种方法虽然代码更复杂,但能够准确地识别字符串内的内容,避免误处理字符串内的特殊字符。第二种方法使用简单的 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐