原文: JFinal使用技巧-百行代码实现JSON字符串解析工具类(无三方依赖)
playground: https://pl.kotl.in/Xr8WAMzRV
import java.math.BigDecimal object JsonParser { private const val MAX_DEPTH = 512 @Suppress("UNCHECKED_CAST") fun parseMap(jsonString: String): Map<String, Any?> { return parse(jsonString) as Map<String, Any?> } @Suppress("UNCHECKED_CAST") fun parseList(jsonString: String): List<Any?> { return parse(jsonString) as List<Any?> } fun parse(jsonString: String): Any? { return Parser(jsonString.trim()).parseValue(0) } private class Parser(private val jsonStr: String) { private var index: Int = 0 fun parseValue(depth: Int): Any? { if (depth > MAX_DEPTH) throw JsonException("Exceeded maximum nesting depth", index) skipWhitespace() if (index >= jsonStr.length) throw JsonException("Unexpected end of JSON", index) return when (val current = jsonStr[index]) { '{' -> parseObject(depth + 1) '[' -> parseArray(depth + 1) '"' -> parseString() in '0'..'9', '-' -> parseNumber() 't', 'f' -> parseBoolean() 'n' -> parseNull() else -> throw JsonException("Unexpected character '$current'", index) } } private fun skipWhitespace() { while (index < jsonStr.length && jsonStr[index].isJsonWhitespace()) { index++ } } private fun parseString(): String { val startIndex = ++index // Skip opening quote val sb = StringBuilder() while (index < jsonStr.length) { when (val c = jsonStr[index++]) { '"' -> return sb.toString() '\\' -> handleEscapeSequence(sb) else -> { if (c.code < 0x20) { throw JsonException( "Unescaped control character: 0x${c.code.toString(16)}", index - 1, ) } sb.append(c) } } } throw JsonException("Unterminated string starting at $startIndex", startIndex) } private fun handleEscapeSequence(sb: StringBuilder) { if (index >= jsonStr.length) throw JsonException("Unterminated escape sequence", index - 1) when (val esc = jsonStr[index++]) { '"', '\\', '/' -> sb.append(esc) 'b' -> sb.append('\b') 'f' -> sb.append('\u000C') 'n' -> sb.append('\n') 'r' -> sb.append('\r') 't' -> sb.append('\t') 'u' -> parseUnicodeEscape(sb) else -> throw JsonException("Invalid escape character '$esc'", index - 1) } } private fun parseUnicodeEscape(sb: StringBuilder) { if (index + 4 > jsonStr.length) throw JsonException("Incomplete unicode escape", index - 1) val hex = jsonStr.substring(index, index + 4) index += 4 try { val codePoint = hex.toInt(16) // Handle surrogate pairs if (codePoint in 0xD800..0xDBFF) { // High surrogate if (index + 6 > jsonStr.length || jsonStr[index] != '\\' || jsonStr[index + 1] != 'u') { throw JsonException("Missing low surrogate", index - 4) } index += 2 // Skip \u val lowHex = jsonStr.substring(index, index + 4) index += 4 val lowCode = lowHex.toInt(16) if (lowCode !in 0xDC00..0xDFFF) { throw JsonException("Invalid low surrogate: \\u$lowHex", index - 4) } val fullCode = 0x10000 + (codePoint - 0xD800) * 0x400 + (lowCode - 0xDC00) sb.append(Character.toChars(fullCode)) } else { sb.append(Character.toChars(codePoint)) } } catch (e: NumberFormatException) { throw JsonException("Invalid unicode escape '\\u$hex'", index - 4) } } private fun parseObject(depth: Int): Map<String, Any?> { val map = LinkedHashMap<String, Any?>() index++ // Skip '{' skipWhitespace() if (consumeIf('}')) { return map } while (true) { skipWhitespace() val key = parseString() if (map.containsKey(key)) throw JsonException("Duplicate key '$key'", index) skipWhitespace() if (!consumeIf(':')) { throw JsonException("Expected ':' after key", index) } val value = parseValue(depth) map[key] = value skipWhitespace() if (consumeIf('}')) { return map } if (!consumeIf(',')) { throw JsonException("Expected ',' or '}'", index) } skipWhitespace() if (jsonStr.getOrNull(index) == '}') { throw JsonException("Trailing comma in object", index) } } } private fun parseArray(depth: Int): List<Any?> { val list = ArrayList<Any?>() index++ // Skip '[' skipWhitespace() if (consumeIf(']')) { return list } while (true) { skipWhitespace() list.add(parseValue(depth)) skipWhitespace() if (consumeIf(']')) { return list } if (!consumeIf(',')) { throw JsonException("Expected ',' or ']'", index) } skipWhitespace() if (jsonStr.getOrNull(index) == ']') { throw JsonException("Trailing comma in array", index) } } } private fun parseNumber(): Number { val start = index var hasExponent = false var hasDecimal = false if (jsonStr[index] == '-') index++ // Check leading zero if (index < jsonStr.length && jsonStr[index] == '0') { index++ if (index < jsonStr.length && jsonStr[index] in '0'..'9') { throw JsonException("Leading zeros not allowed", index) } } else { while (index < jsonStr.length && jsonStr[index] in '0'..'9') { index++ } } // Decimal part if (index < jsonStr.length && jsonStr[index] == '.') { hasDecimal = true index++ if (index >= jsonStr.length || jsonStr[index] !in '0'..'9') { throw JsonException("Expected digit after decimal point", index) } while (index < jsonStr.length && jsonStr[index] in '0'..'9') { index++ } } // Exponent part if (index < jsonStr.length && jsonStr[index] in "eE") { hasExponent = true index++ if (index < jsonStr.length && jsonStr[index] in "+-") { index++ } if (index >= jsonStr.length || jsonStr[index] !in '0'..'9') { throw JsonException("Expected digit in exponent", index) } while (index < jsonStr.length && jsonStr[index] in '0'..'9') { index++ } } val numStr = jsonStr.substring(start, index) return try { when { hasDecimal || hasExponent -> numStr.toDouble() numStr.toLongOrNull() != null -> { val longVal = numStr.toLong() if (longVal >= Int.MIN_VALUE && longVal <= Int.MAX_VALUE) longVal.toInt() else longVal } else -> BigDecimal(numStr) } } catch (e: NumberFormatException) { throw JsonException("Invalid number format: '$numStr'", start) } } private fun parseBoolean(): Boolean { return when { jsonStr.startsWith("true", index) -> { index += 4 true } jsonStr.startsWith("false", index) -> { index += 5 false } else -> throw JsonException("Expected boolean value", index) } } private fun parseNull(): Any? { if (jsonStr.startsWith("null", index)) { index += 4 return null } throw JsonException("Expected null value", index) } private fun Char.isJsonWhitespace() = this in " \t\n\r" private fun consumeIf(expected: Char): Boolean { if (index < jsonStr.length && jsonStr[index] == expected) { index++ return true } return false } } class JsonException(message: String, position: Int) : RuntimeException("$message at position $position") } /* * ===== JSON Parser Test ===== * ✅ PASS: Empty object * ✅ PASS: Simple object * ✅ PASS: Nested structures * ✅ PASS: Empty array * ✅ PASS: Mixed type array * ✅ PASS: Unicode escape * ✅ PASS: Scientific notation * * ===== Error Handling Tests ===== * ✅ PASS: Invalid token - Unexpected character 'u' at position 8 * ✅ PASS: Trailing comma - Unexpected character ',' at position 3 * ✅ PASS: Missing comma - Expected ',' or '}' at position 7 * ✅ PASS: Duplicate key - Duplicate key 'dup' at position 15 * ✅ PASS: Unclosed array - Unexpected end of JSON at position 6 * ✅ PASS: Leading zero - Leading zeros not allowed at position 9 * ✅ PASS: Control character - Unescaped control character: 0x0 at position 17 * ✅ PASS: Extra bracket - Trailing comma in array at position 7 * ✅ PASS: Excess nesting - Exceeded maximum nesting depth at position 522 */ fun main() { val parser = JsonParser println("===== JSON Parser Test =====") // Test 1: 空对象 testParseMap(parser, "{}", emptyMap(), "Empty object") // Test 2: 简单对象 testParseMap( parser, """{"name":"Alice","age":25,"active":true}""", mapOf("name" to "Alice", "age" to 25, "active" to true), "Simple object", ) // Test 3: 嵌套对象 testParseMap( parser, """{"user":{"id":1,"prefs":{"theme":"dark"}},"tags":["kotlin","json"]}""", mapOf( "user" to mapOf("id" to 1, "prefs" to mapOf("theme" to "dark")), "tags" to listOf("kotlin", "json"), ), "Nested structures", ) // Test 4: 空数组 testParseList(parser, "[]", emptyList(), "Empty array") // Test 5: 混合类型数组 testParseList( parser, """[null, 42, -3.14, "text", true, false]""", listOf(null, 42, -3.14, "text", true, false), "Mixed type array", ) // Test 6: Unicode 转义 testParseString(parser, """"Hello\u0020World!\uD83D\uDE00"""", "Hello World!😀", "Unicode escape") // Test 7: 科学计数法数字 testParseNumber(parser, "1.23e4", 12300.0, "Scientific notation") // Test 8: 错误处理 testErrorHandling(parser) } private fun testParseMap( parser: JsonParser, json: String, expected: Map<String, Any?>, name: String, ) { try { val result = parser.parseMap(json) if (result == expected) { println("✅ PASS: $name") } else { println("❌ FAIL: $name\n Expected: $expected\n Got: $result") } } catch (e: Exception) { println("❌ ERROR: $name - ${e.message}") } } private fun testParseList(parser: JsonParser, json: String, expected: List<Any?>, name: String) { try { val result = parser.parseList(json) if (result == expected) { println("✅ PASS: $name") } else { println("❌ FAIL: $name\n Expected: $expected\n Got: $result") } } catch (e: Exception) { println("❌ ERROR: $name - ${e.message}") } } private fun testParseString(parser: JsonParser, json: String, expected: String, name: String) { try { val result = parser.parse(json) as String if (result == expected) { println("✅ PASS: $name") } else { println("❌ FAIL: $name\n Expected: '$expected'\n Got: '$result'") } } catch (e: Exception) { println("❌ ERROR: $name - ${e.message}") } } private fun testParseNumber(parser: JsonParser, json: String, expected: Number, name: String) { try { val result = parser.parse(json) as Number if (result.toDouble() == expected.toDouble()) { println("✅ PASS: $name") } else { println("❌ FAIL: $name\n Expected: $expected\n Got: $result") } } catch (e: Exception) { println("❌ ERROR: $name - ${e.message}") } } private fun testErrorHandling(parser: JsonParser) { // 创建超过512层嵌套的JSON val deepNested = buildString { append("{\"nested\":") repeat(600) { append('[') } append("123") repeat(600) { append(']') } append('}') } // 创建包含未转义控制字符的JSON val controlCharJson = buildString { append("{\"control\": \"text") append(0.toChar()) // 添加未转义的控制字符 (U+0000) append("\"}") } val tests = listOf( """{"key": undefined}""" to "Invalid token", """[1,,2]""" to "Trailing comma", """{"a":1 "b":2}""" to "Missing comma", """{"dup":1, "dup":2}""" to "Duplicate key", """[1,2,3""" to "Unclosed array", // 修正为未闭合数组 """{"num": 012}""" to "Leading zero", controlCharJson to "Control character", // 使用包含实际控制字符的JSON """[1,2,3,]]""" to "Extra bracket", deepNested to "Excess nesting", // 使用深层嵌套的JSON ) println("\n===== Error Handling Tests =====") tests.forEach { (json, testName) -> try { parser.parse(json) println("❌ FAIL: $testName - Expected error but parsed successfully") } catch (e: JsonParser.JsonException) { println("✅ PASS: $testName - ${e.message}") } catch (e: Exception) { println("❌ ERROR: $testName - Wrong exception: ${e.javaClass.simpleName}") } } }