在这里插入图片描述

目录

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

概述

本文档介绍如何在 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() 函数遍历字符串中的每个字符,并计算满足条件的字符个数。第一行统计所有开放的大括号 { 的数量,第二行统计所有关闭的大括号 } 的数量。第三行通过比较两个计数值是否相等来判断括号是否匹配。如果开放括号数等于关闭括号数,则 bracesMatchedtrue,表示括号配对正确。这是一个简单但有效的括号匹配验证方法,虽然不能检测括号的嵌套顺序是否正确,但能快速发现明显的不匹配情况。

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() 字符串重复

关键转换点

  1. 字符串遍历:转换为 for 循环
  2. 正则表达式:转换为 JavaScript RegExp
  3. 集合操作:转换为数组操作
  4. 字符串处理:保持功能一致

算法扩展

扩展 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

下一步

  1. 实现 JSON 压缩功能
  2. 添加 JSON 路径查询
  3. 实现 JSON 转换
  4. 添加 JSON 差异对比
  5. 实现流式处理大文件

参考资源

Logo

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

更多推荐