原文: 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}")
}
}
}