KMP 实现鸿蒙跨端:Kotlin JSON格式化和验证算法
本文档介绍如何在鸿蒙跨端开发中实现一个完整的JSON格式化和验证算法系统。这个案例展示了如何使用 Kotlin 的字符串操作、正则表达式和结构分析来创建一个功能丰富的JSON处理工具。通过 KMP,这个算法可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。✅ 使用 count() 进行统计✅ 使用正则表达式提取数据✅ 保护字符串内容不被处理✅

目录
- 概述
- 算法功能
- 核心实现
- 实战案例
- 编译过程详解
- 算法扩展
- 最佳实践
- 常见问题
概述
本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个完整的JSON格式化和验证算法系统。这个案例展示了如何使用 Kotlin 的字符串操作、正则表达式和结构分析来创建一个功能丰富的JSON处理工具。通过 KMP,这个算法可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。
算法的特点
- 格式化功能:自动格式化 JSON 为易读的形式
- 验证功能:检查 JSON 的有效性和括号匹配
- 结构分析:提供详细的 JSON 结构分析
- 键值提取:自动提取 JSON 中的所有键
- 跨端兼容:一份 Kotlin 代码可同时服务多个平台
算法功能
1. JSON验证
- 格式检查:验证 JSON 是否以 { 或 [ 开头和结尾
- 括号匹配:检查大括号和方括号是否匹配
- 结构完整性:验证 JSON 结构是否完整
2. JSON格式化
- 缩进处理:添加适当的缩进和换行
- 空格处理:在冒号后添加空格
- 字符串保护:保护字符串中的特殊字符
3. 结构分析
- 对象统计:统计 JSON 中的对象数量
- 数组统计:统计 JSON 中的数组数量
- 深度计算:计算 JSON 的最大嵌套深度
- 键值提取:提取所有的键名
4. 值类型统计
- 字符串值:统计字符串类型的值
- 数值:统计数字类型的值
- 布尔值:统计布尔类型的值
- 空值:统计 null 值
5. 大小对比
- 原始大小:原始 JSON 的字节数
- 格式化后:格式化后的字节数
- 大小变化:显示大小的变化
核心实现
1. JSON验证
val isValidJson = try {
val trimmed = inputJson.trim()
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"))
} catch (e: Exception) {
false
}
代码说明:
这段代码用于验证输入的字符串是否为有效的 JSON 格式。首先使用 trim() 方法移除字符串两端的空白字符,然后检查修剪后的字符串是否以 { 开头并以 } 结尾(表示 JSON 对象),或者以 [ 开头并以 ] 结尾(表示 JSON 数组)。这是一个基础的格式检查,不涉及复杂的语法验证。整个验证过程被包裹在 try-catch 块中,如果在处理过程中发生任何异常,函数会返回 false,表示 JSON 无效。这种防御性编程方式确保了代码的健壮性。
2. 括号匹配检查
val openBraces = inputJson.count { it == '{' }
val closeBraces = inputJson.count { it == '}' }
val bracesMatched = openBraces == closeBraces
代码说明:
这段代码通过计数的方式检查 JSON 中的大括号是否匹配。count() 函数遍历字符串中的每个字符,并计算满足条件的字符个数。第一行统计所有开放的大括号 { 的数量,第二行统计所有关闭的大括号 } 的数量。第三行通过比较两个计数值是否相等来判断括号是否匹配。如果开放括号数等于关闭括号数,则 bracesMatched 为 true,表示括号配对正确。这是一个简单但有效的括号匹配验证方法,虽然不能检测括号的嵌套顺序是否正确,但能快速发现明显的不匹配情况。
3. 键值提取
val keys = mutableListOf<String>()
val keyPattern = Regex("\"([^\"]+)\"\\s*:")
keyPattern.findAll(inputJson).forEach { match ->
keys.add(match.groupValues[1])
}
代码说明:
这段代码使用正则表达式从 JSON 字符串中提取所有的键名。首先创建一个可变列表 keys 来存储提取的键。然后定义一个正则表达式 keyPattern,其中 \" 匹配引号,([^\"]+) 是一个捕获组,用于匹配引号内的任何非引号字符(即键名),\\s*: 匹配零个或多个空白字符后跟冒号。findAll() 方法在整个 JSON 字符串中查找所有匹配该模式的部分,返回一个序列。对于每个匹配结果,使用 match.groupValues[1] 获取第一个捕获组的内容(即键名),并将其添加到 keys 列表中。这种方法能够有效地从复杂的 JSON 结构中提取所有键名。
4. 深度计算
var maxDepth = 0
var currentDepth = 0
for (char in inputJson) {
when (char) {
'{', '[' -> {
currentDepth++
maxDepth = maxOf(maxDepth, currentDepth)
}
'}', ']' -> currentDepth--
}
}
代码说明:
这段代码计算 JSON 的最大嵌套深度,这对于理解 JSON 结构的复杂性很有帮助。初始化两个变量:maxDepth 用于记录遇到的最大深度,currentDepth 用于追踪当前的嵌套深度。遍历 JSON 字符串中的每个字符,使用 when 表达式进行模式匹配。当遇到开放括号 { 或 [ 时,表示进入了一个新的嵌套层级,所以 currentDepth 加 1,然后使用 maxOf() 函数更新 maxDepth 为当前深度和之前最大深度中的较大值。当遇到关闭括号 } 或 ] 时,表示退出了一个嵌套层级,所以 currentDepth 减 1。最后 maxDepth 就是整个 JSON 的最大嵌套深度。
5. JSON格式化
fun formatJson(json: String): String {
val result = StringBuilder()
var indent = 0
var inString = false
var i = 0
while (i < json.length) {
val char = json[i]
when {
char == '"' && (i == 0 || json[i - 1] != '\\') -> {
inString = !inString
result.append(char)
}
!inString && (char == '{' || char == '[') -> {
result.append(char)
indent++
result.append("\n")
result.append(" ".repeat(indent))
}
!inString && (char == '}' || char == ']') -> {
indent--
result.append("\n")
result.append(" ".repeat(indent))
result.append(char)
}
!inString && char == ',' -> {
result.append(char)
result.append("\n")
result.append(" ".repeat(indent))
}
!inString && char == ':' -> {
result.append(char)
result.append(" ")
}
char != ' ' || inString -> {
result.append(char)
}
}
i++
}
return result.toString()
}
代码说明:
这是一个完整的 JSON 格式化函数,将紧凑的 JSON 转换为易读的格式。函数使用 StringBuilder 来高效地构建结果字符串。indent 变量追踪当前的缩进级别,inString 标志用于追踪当前是否在字符串内部(这很重要,因为字符串内的特殊字符不应被处理)。遍历 JSON 的每个字符,使用 when 表达式处理不同的情况。当遇到引号时,检查前一个字符是否为反斜杠(转义符),如果不是,则切换 inString 标志。当遇到开放括号且不在字符串内时,追加该字符,增加缩进级别,然后添加换行和相应的缩进空格。当遇到关闭括号时,先减少缩进级别,然后添加换行和缩进。当遇到逗号时,追加逗号并添加换行和缩进。当遇到冒号时,追加冒号并添加一个空格。对于其他字符,只有在不是空格或在字符串内时才追加。最后返回格式化后的字符串。
实战案例
案例:完整的JSON格式化和验证算法
Kotlin 源代码
代码说明:
这是 JSON 格式化和验证算法的完整 Kotlin 实现,使用 @JsExport 装饰器将其导出为 JavaScript 可调用的函数。函数接收一个 JSON 字符串参数,默认值为一个示例 JSON 对象。函数执行多个步骤:首先验证 JSON 格式的有效性,然后进行格式化处理,接着统计 JSON 的结构信息(如括号数量、冒号数量等),检查括号是否匹配,提取所有的键名,计算 JSON 的最大嵌套深度,统计不同类型值的数量,最后比较原始 JSON 和格式化后 JSON 的大小。函数返回一个详细的分析报告,包含所有这些信息,使用 emoji 和分隔符使输出更加直观。
@OptIn(ExperimentalJsExport::class)
@JsExport
fun jsonFormatterValidator(inputJson: String = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}"): String {
if (inputJson.isEmpty()) {
return "❌ 错误: JSON 不能为空\n请输入要格式化的 JSON"
}
// 1. 验证 JSON 格式
val isValidJson = try {
val trimmed = inputJson.trim()
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"))
} catch (e: Exception) {
false
}
// 2. 格式化 JSON
val formatted = try {
formatJson(inputJson)
} catch (e: Exception) {
"格式化失败: ${e.message}"
}
// 3. 统计 JSON 结构
val braceCount = inputJson.count { it == '{' }
val bracketCount = inputJson.count { it == '[' }
val colonCount = inputJson.count { it == ':' }
val commaCount = inputJson.count { it == ',' }
val quoteCount = inputJson.count { it == '"' }
// 4. 检查括号匹配
val openBraces = inputJson.count { it == '{' }
val closeBraces = inputJson.count { it == '}' }
val openBrackets = inputJson.count { it == '[' }
val closeBrackets = inputJson.count { it == ']' }
val bracesMatched = openBraces == closeBraces
val bracketsMatched = openBrackets == closeBrackets
// 5. 提取 JSON 键
val keys = mutableListOf<String>()
val keyPattern = Regex("\"([^\"]+)\"\\s*:")
keyPattern.findAll(inputJson).forEach { match ->
keys.add(match.groupValues[1])
}
// 6. 计算 JSON 深度
var maxDepth = 0
var currentDepth = 0
for (char in inputJson) {
when (char) {
'{', '[' -> {
currentDepth++
maxDepth = maxOf(maxDepth, currentDepth)
}
'}', ']' -> currentDepth--
}
}
// 7. 统计值的类型
val stringValues = inputJson.split(":").count { it.contains("\"") }
val numberValues = inputJson.split(":").count {
val value = it.trim().split(",")[0].trim()
value.matches(Regex("-?\\d+(\\.\\d+)?"))
}
val booleanValues = inputJson.count { it.toString().matches(Regex("true|false")) }
val nullValues = inputJson.split(":").count { it.contains("null") }
// 8. 原始 JSON 大小
val originalSize = inputJson.length
val formattedSize = formatted.length
return "📋 JSON 格式化和验证\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"1️⃣ 验证结果:\n" +
" JSON 有效性: ${if (isValidJson) "✅ 有效" else "❌ 无效"}\n" +
" 大括号匹配: ${if (bracesMatched) "✅ 匹配" else "❌ 不匹配"}\n" +
" 方括号匹配: ${if (bracketsMatched) "✅ 匹配" else "❌ 不匹配"}\n\n" +
"2️⃣ 结构分析:\n" +
" 对象数量: $braceCount\n" +
" 数组数量: $bracketCount\n" +
" 冒号数量: $colonCount\n" +
" 逗号数量: $commaCount\n" +
" 引号数量: $quoteCount\n" +
" 最大深度: $maxDepth\n\n" +
"3️⃣ 键值统计:\n" +
" 键的数量: ${keys.size}\n" +
" 键列表: ${if (keys.isEmpty()) "无" else keys.joinToString(", ")}\n\n" +
"4️⃣ 值类型统计:\n" +
" 字符串值: $stringValues 个\n" +
" 数值: $numberValues 个\n" +
" 布尔值: $booleanValues 个\n" +
" 空值: $nullValues 个\n\n" +
"5️⃣ 大小对比:\n" +
" 原始大小: $originalSize 字节\n" +
" 格式化后: $formattedSize 字节\n\n" +
"6️⃣ 格式化结果:\n" +
"$formatted\n\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"✅ 处理完成!"
}
fun formatJson(json: String): String {
val result = StringBuilder()
var indent = 0
var inString = false
var i = 0
while (i < json.length) {
val char = json[i]
when {
char == '"' && (i == 0 || json[i - 1] != '\\') -> {
inString = !inString
result.append(char)
}
!inString && (char == '{' || char == '[') -> {
result.append(char)
indent++
result.append("\n")
result.append(" ".repeat(indent))
}
!inString && (char == '}' || char == ']') -> {
indent--
result.append("\n")
result.append(" ".repeat(indent))
result.append(char)
}
!inString && char == ',' -> {
result.append(char)
result.append("\n")
result.append(" ".repeat(indent))
}
!inString && char == ':' -> {
result.append(char)
result.append(" ")
}
char != ' ' || inString -> {
result.append(char)
}
}
i++
}
return result.toString()
}
ArkTS 调用代码(带输入框)
代码说明:
这是 OpenHarmony ArkTS 页面的完整实现,展示了如何集成和调用 Kotlin 编译生成的 JSON 格式化函数。首先通过 import 语句从 ./hellokjs 模块导入 jsonFormatterValidator 函数。页面使用 @Entry 和 @Component 装饰器定义为可入口的组件。定义了四个响应式状态变量:message 显示操作状态,results 存储格式化结果,caseTitle 显示标题,inputText 存储用户输入的 JSON。aboutToAppear() 生命周期钩子在页面加载时调用 loadResults() 进行初始化。loadResults() 方法调用 Kotlin 函数进行格式化处理,并将结果存储在 results 数组中。build() 方法定义了完整的 UI 布局,包括顶部标题栏、输入框、格式化按钮、结果展示区域和底部操作按钮,使用了 Column、Row、Text、TextInput、Button 等组件构建了一个功能完整的用户界面。
import { jsonFormatterValidator } from './hellokjs';
@Entry
@Component
struct Index {
@State message: string = '加载中...';
@State results: string[] = [];
@State caseTitle: string = 'JSON格式化和验证';
@State inputText: string = '{"name":"John","age":30,"city":"New York"}';
aboutToAppear(): void {
this.loadResults();
}
loadResults(): void {
try {
const results: string[] = [];
const algorithmResult = jsonFormatterValidator(this.inputText);
results.push(algorithmResult);
this.results = results;
this.message = '✓ 处理完成';
} catch (error) {
this.message = `✗ 错误: ${error}`;
}
}
build() {
Column() {
// 顶部标题栏
Row() {
Text('KMP 鸿蒙跨端')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
Spacer()
Text('Kotlin 案例')
.fontSize(14)
.fontColor(Color.White)
}
.width('100%')
.height(50)
.backgroundColor('#3b82f6')
.padding({ left: 20, right: 20 })
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceBetween)
// 案例标题
Column() {
Text(this.caseTitle)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1f2937')
Text(this.message)
.fontSize(13)
.fontColor('#6b7280')
.margin({ top: 5 })
}
.width('100%')
.padding({ left: 20, right: 20, top: 20, bottom: 15 })
.alignItems(HorizontalAlign.Start)
// 输入框区域
Column() {
Text('输入 JSON:')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#1f2937')
.margin({ bottom: 8 })
TextInput({ placeholder: '输入要格式化的 JSON...', text: this.inputText })
.width('100%')
.height(60)
.padding(12)
.border({ width: 1, color: '#d1d5db' })
.borderRadius(6)
.onChange((value: string) => {
this.inputText = value
})
Button('格式化')
.width('100%')
.height(40)
.margin({ top: 12 })
.backgroundColor('#3b82f6')
.fontColor(Color.White)
.onClick(() => {
this.loadResults()
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
// 结果显示区域
Scroll() {
Column() {
ForEach(this.results, (result: string) => {
Column() {
Text(result)
.fontSize(13)
.fontFamily('monospace')
.fontColor('#374151')
.width('100%')
.margin({ top: 10 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.border({ width: 1, color: '#e5e7eb' })
.borderRadius(8)
.margin({ bottom: 12 })
})
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.layoutWeight(1)
.width('100%')
// 底部按钮区域
Row() {
Button('示例 JSON')
.width('48%')
.height(44)
.backgroundColor('#10b981')
.fontColor(Color.White)
.fontSize(14)
.onClick(() => {
this.inputText = '{"name":"John","age":30,"city":"New York"}'
this.loadResults()
})
Button('清空')
.width('48%')
.height(44)
.backgroundColor('#6b7280')
.fontColor(Color.White)
.fontSize(14)
.onClick(() => {
this.inputText = ''
this.results = []
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#f9fafb')
}
}
编译过程详解
Kotlin 到 JavaScript 的转换
| Kotlin 特性 | JavaScript 等价物 |
|---|---|
| count() | 条件计数循环 |
| Regex.findAll() | 字符串 match 方法 |
| StringBuilder | 字符串连接 |
| when 表达式 | switch 或 if-else |
| repeat() | 字符串重复 |
关键转换点
- 字符串遍历:转换为 for 循环
- 正则表达式:转换为 JavaScript RegExp
- 集合操作:转换为数组操作
- 字符串处理:保持功能一致
算法扩展
扩展 1:添加 JSON 压缩
fun compressJson(json: String): String {
return json.replace(Regex("\\s+"), "")
.replace(Regex(":\\s+"), ":")
.replace(Regex(",\\s+"), ",")
}
代码说明:
这个函数用于压缩 JSON,移除所有不必要的空白字符以减小文件大小。函数使用链式调用 replace() 方法,每次调用都使用正则表达式替换特定的模式。第一个 replace() 使用正则表达式 \\s+ 匹配一个或多个空白字符(包括空格、制表符、换行符等),并将其替换为空字符串,这样可以移除所有空白。第二个 replace() 使用 :\\s+ 匹配冒号后跟一个或多个空白字符,替换为单个冒号,确保 JSON 键值对之间没有多余空格。第三个 replace() 使用 ,\\s+ 匹配逗号后跟一个或多个空白字符,替换为单个逗号。经过这三个替换操作后,JSON 就被压缩成了最紧凑的形式,通常可以减少 30-50% 的文件大小。
扩展 2:添加 JSON 路径查询
fun getJsonValue(json: String, path: String): String {
val keys = path.split(".")
var current = json
for (key in keys) {
val pattern = Regex("\"$key\"\\s*:\\s*([^,}\\]]+)")
val match = pattern.find(current)
if (match != null) {
current = match.groupValues[1]
} else {
return "未找到"
}
}
return current
}
代码说明:
这个函数实现了 JSON 路径查询功能,允许用户通过点号分隔的路径(如 “user.name”)来访问嵌套的 JSON 值。函数首先将输入的路径字符串按点号分割成多个键名。然后使用循环逐个遍历这些键。对于每个键,创建一个正则表达式来匹配该键及其对应的值。正则表达式 \"$key\"\\s*:\\s*([^,}\\]]+) 匹配引号包围的键名、可选的空白、冒号、可选的空白,然后捕获值部分(任何非逗号、非右大括号、非右方括号的字符)。使用 pattern.find() 在当前 JSON 字符串中查找匹配项。如果找到匹配,将当前 JSON 更新为匹配的值部分,继续处理下一个键。如果没有找到匹配,说明路径不存在,返回 “未找到”。最后返回找到的最终值。这种方法虽然不如完整的 JSON 解析器那样健壮,但对于简单的路径查询非常有效。
扩展 3:添加 JSON 转换
fun jsonToMap(json: String): Map<String, String> {
val result = mutableMapOf<String, String>()
val keyPattern = Regex("\"([^\"]+)\"\\s*:\\s*([^,}]+)")
keyPattern.findAll(json).forEach { match ->
result[match.groupValues[1]] = match.groupValues[2]
}
return result
}
代码说明:
这个函数将 JSON 对象转换为 Kotlin 的 Map 数据结构,便于在代码中进行进一步处理。函数创建一个可变的 Map 来存储键值对。定义一个正则表达式 \"([^\"]+)\"\\s*:\\s*([^,}]+),其中第一个捕获组 ([^\"]+) 匹配键名(引号内的任何非引号字符),第二个捕获组 ([^,}]+) 匹配值(任何非逗号、非右大括号的字符)。使用 findAll() 在 JSON 字符串中查找所有匹配该模式的键值对。对于每个匹配,使用 match.groupValues[1] 获取键名,match.groupValues[2] 获取值,并将其添加到 Map 中。最后返回填充好的 Map。这种转换方式使得可以像访问普通 Kotlin Map 一样访问 JSON 数据,提高了代码的可读性和易用性。
扩展 4:添加 JSON 差异对比
fun compareJson(json1: String, json2: String): List<String> {
val differences = mutableListOf<String>()
val keys1 = extractKeys(json1)
val keys2 = extractKeys(json2)
for (key in keys1) {
if (!keys2.contains(key)) {
differences.add("键 '$key' 仅在第一个 JSON 中存在")
}
}
return differences
}
代码说明:
这个函数实现了两个 JSON 对象之间的差异对比功能,用于找出两个 JSON 中不同的键。函数创建一个可变列表来存储发现的差异。首先调用 extractKeys() 函数分别从两个 JSON 字符串中提取所有的键名,得到两个键名列表。然后遍历第一个 JSON 的所有键,对于每个键,检查它是否存在于第二个 JSON 的键列表中。如果第一个 JSON 中的某个键不在第二个 JSON 中,就将这个差异信息添加到差异列表中。最后返回包含所有差异的列表。这个函数只检查了第一个 JSON 中独有的键,如果需要完整的对比(包括第二个 JSON 独有的键),可以再添加一个反向的检查循环。
最佳实践
1. 使用 count() 统计
// ✅ 好:使用 count()
val braceCount = json.count { it == '{' }
// ❌ 不好:使用 filter().size
val braceCount = json.filter { it == '{' }.size
代码说明:
这个示例对比了两种统计字符数量的方法。第一种方法使用 count() 函数,它直接遍历字符串并计算满足条件的字符数量,效率更高,因为它只需要一次遍历。第二种方法先使用 filter() 创建一个包含所有匹配字符的新列表,然后获取这个列表的大小,这会产生额外的内存开销和两次遍历。对于大型 JSON 字符串,使用 count() 的性能优势会更加明显。最佳实践是优先使用 count() 来进行统计操作。
2. 使用正则表达式提取
// ✅ 好:使用 Regex.findAll()
val keys = mutableListOf<String>()
Regex("\"([^\"]+)\"\\s*:").findAll(json).forEach { match ->
keys.add(match.groupValues[1])
}
// ❌ 不好:手动查找
var start = 0
while (true) {
start = json.indexOf("\"", start)
// 复杂的查找逻辑
}
代码说明:
这个示例对比了两种从 JSON 中提取键名的方法。第一种方法使用正则表达式 findAll() 来查找所有匹配的键名模式,代码简洁清晰,易于维护。正则表达式 \"([^\"]+)\"\\s*: 准确地匹配了 JSON 键的格式。第二种方法使用手动查找,需要编写复杂的循环逻辑来处理各种边界情况,容易出错且难以维护。使用正则表达式的方法不仅代码更简洁,而且更加可靠和高效。最佳实践是在处理结构化文本时优先使用正则表达式。
3. 保护字符串内容
// ✅ 好:检查是否在字符串中
var inString = false
for (char in json) {
if (char == '"') inString = !inString
if (!inString && char == '{') { /* 处理 */ }
}
// ❌ 不好:忽略字符串内容
for (char in json) {
if (char == '{') { /* 处理 */ }
}
代码说明:
这个示例展示了处理 JSON 时保护字符串内容的重要性。第一种方法使用一个布尔标志 inString 来追踪当前是否在字符串内部。当遇到引号时,切换这个标志。只有当不在字符串内部时,才处理特殊字符如 {。这样可以避免误处理字符串内部的特殊字符。例如,如果 JSON 值中包含 { 字符,第二种方法会错误地将其识别为 JSON 对象的开始。第二种方法忽略了字符串内容的保护,会导致错误的解析结果。最佳实践是在处理 JSON 或其他结构化文本时,始终要追踪当前是否在字符串内部。
4. 使用 StringBuilder 构建结果
// ✅ 好:使用 StringBuilder
val result = StringBuilder()
for (char in json) {
result.append(char)
}
// ❌ 不好:字符串连接
var result = ""
for (char in json) {
result += char
}
代码说明:
这个示例对比了两种字符串构建方法的性能差异。第一种方法使用 StringBuilder,它是专门为高效字符串构建设计的可变字符序列。每次调用 append() 都直接添加字符到内部缓冲区,性能高效。第二种方法使用字符串连接操作符 +=,在每次迭代中都会创建一个新的字符串对象,因为 Kotlin 中的字符串是不可变的。对于大型 JSON 字符串,这会导致大量的临时对象创建和内存分配,严重影响性能。使用 StringBuilder 的方法在处理大量字符串操作时性能可以提高数倍。最佳实践是在循环中构建字符串时始终使用 StringBuilder。
常见问题
Q1: 如何处理转义字符?
A: 检查前一个字符是否为反斜杠:
var inString = false
for (i in json.indices) {
if (json[i] == '"' && (i == 0 || json[i - 1] != '\\')) {
inString = !inString
}
}
代码说明:
这段代码展示了如何正确处理 JSON 中的转义字符。在 JSON 中,字符串内的引号需要用反斜杠转义(如 \"),而转义的引号不应该被视为字符串的结束标记。代码使用一个布尔标志 inString 来追踪当前是否在字符串内部。对于每个引号字符,检查它前面的字符是否为反斜杠。条件 (i == 0 || json[i - 1] != '\\') 确保了只有当引号不是转义的时候,才切换 inString 标志。这样可以正确处理包含转义引号的字符串,避免错误地识别转义引号为字符串边界。
Q2: 如何提取嵌套的 JSON 值?
A: 使用递归或栈来跟踪深度:
fun getNestedValue(json: String, path: List<String>): String {
var current = json
for (key in path) {
val pattern = Regex("\"$key\"\\s*:\\s*\\{([^}]+)\\}")
val match = pattern.find(current)
if (match != null) {
current = "{${match.groupValues[1]}}"
}
}
return current
}
代码说明:
这个函数展示了如何从嵌套的 JSON 结构中提取值。函数接收一个 JSON 字符串和一个路径列表(如 ["user", "profile"]),表示要访问的嵌套键。使用循环逐个遍历路径中的每个键。对于每个键,创建一个正则表达式来匹配该键及其对应的值(假设值是一个 JSON 对象)。正则表达式 \"$key\"\\s*:\\s*\\{([^}]+)\\} 匹配键名、冒号和一个 JSON 对象,并捕获对象的内容。如果找到匹配,将当前 JSON 更新为这个嵌套对象,继续处理下一个键。这样逐层深入,最终返回目标路径指向的值。这种方法适用于简单的嵌套结构,对于更复杂的嵌套可能需要更完善的 JSON 解析器。
Q3: 如何验证 JSON 的完整性?
A: 检查所有括号是否匹配:
fun isValidJson(json: String): Boolean {
var braceCount = 0
var bracketCount = 0
var inString = false
for (i in json.indices) {
if (json[i] == '"' && (i == 0 || json[i - 1] != '\\')) {
inString = !inString
}
if (!inString) {
when (json[i]) {
'{' -> braceCount++
'}' -> braceCount--
'[' -> bracketCount++
']' -> bracketCount--
}
}
}
return braceCount == 0 && bracketCount == 0
}
代码说明:
这个函数提供了一个更完善的 JSON 验证方法,不仅检查格式,还验证括号的完整匹配。函数使用两个计数器分别追踪大括号和方括号的匹配情况,以及一个标志来追踪当前是否在字符串内部。遍历 JSON 的每个字符,首先检查是否遇到引号(考虑转义情况),如果是则切换 inString 标志。只有当不在字符串内部时,才处理括号。对于每个开放括号,相应的计数器加 1;对于每个关闭括号,相应的计数器减 1。最后,如果两个计数器都为 0,说明所有括号都正确匹配,JSON 是有效的。这种方法比简单的格式检查更加严格,能够发现更多的 JSON 错误。
Q4: 如何处理大型 JSON 文件?
A: 使用流式处理而不是一次性加载:
fun processLargeJson(filePath: String): Sequence<String> {
return java.io.File(filePath).useLines { lines ->
lines.filter { it.contains(":") }
.map { it.trim() }
.toList()
.asSequence()
}
}
代码说明:
这个函数展示了如何高效地处理大型 JSON 文件。使用 useLines() 方法以流式方式读取文件,而不是一次性将整个文件加载到内存中。useLines() 返回一个序列,允许逐行处理文件内容。对于每一行,使用 filter() 检查是否包含冒号(这是 JSON 键值对的标志),然后使用 map() 对每一行进行修剪(移除前后空白)。最后将结果转换为列表,然后再转换为序列以便进一步处理。这种流式处理方法的优点是内存使用量恒定,不会因为文件大小而增加,适合处理超大型 JSON 文件。
Q5: 如何实现 JSON 的美化打印?
A: 使用缩进和换行:
fun prettyPrintJson(json: String, indent: Int = 2): String {
val result = StringBuilder()
var indentLevel = 0
var inString = false
for (char in json) {
when {
char == '"' -> {
inString = !inString
result.append(char)
}
!inString && (char == '{' || char == '[') -> {
result.append(char)
indentLevel++
result.append("\n").append(" ".repeat(indentLevel * indent))
}
!inString && (char == '}' || char == ']') -> {
indentLevel--
result.append("\n").append(" ".repeat(indentLevel * indent))
result.append(char)
}
!inString && char == ',' -> {
result.append(char)
result.append("\n").append(" ".repeat(indentLevel * indent))
}
!inString && char == ':' -> {
result.append(char).append(" ")
}
char != ' ' || inString -> {
result.append(char)
}
}
}
return result.toString()
}
代码说明:
这个函数实现了 JSON 的美化打印功能,使紧凑的 JSON 变得易于阅读。函数接收一个 JSON 字符串和一个缩进参数(默认为 2 个空格)。使用 StringBuilder 构建结果,indentLevel 追踪当前的缩进级别,inString 标志追踪是否在字符串内部。遍历 JSON 的每个字符,使用 when 表达式处理不同情况。当遇到引号时,切换 inString 标志。当遇到开放括号且不在字符串内时,追加括号、增加缩进级别、添加换行和相应的缩进空格。当遇到关闭括号时,先减少缩进级别,然后添加换行和缩进,最后追加括号。当遇到逗号时,追加逗号、换行和缩进。当遇到冒号时,追加冒号和一个空格。对于其他字符,只有在不是空格或在字符串内时才追加。最后返回美化后的 JSON 字符串。
总结
关键要点
- ✅ 使用 count() 进行统计
- ✅ 使用正则表达式提取数据
- ✅ 保护字符串内容不被处理
- ✅ 使用 StringBuilder 构建结果
- ✅ KMP 能无缝编译到 JavaScript
下一步
- 实现 JSON 压缩功能
- 添加 JSON 路径查询
- 实现 JSON 转换
- 添加 JSON 差异对比
- 实现流式处理大文件
参考资源
更多推荐



所有评论(0)