当前场景,主要是为了联调,数据结构可能复杂嵌套,不可修改,没有@Serializable之类的注解,需要看到数据的json格式打印。
完善的调试打印工具 (DebugUtils)
以下是一个功能完善的调试工具,专门用于处理复杂嵌套数据结构的 JSON 格式打印:
kotlin
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
object DebugUtils {
// 配置选项
data class DebugConfig(
val maxDepth: Int = 10, // 最大嵌套深度
val maxCollectionSize: Int = 100, // 集合最大显示元素数量
val maxStringLength: Int = 200, // 字符串最大长度
val showNullValues: Boolean = true, // 是否显示 null 值
val showSizeForCollections: Boolean = true, // 是否显示集合大小
val prettyPrint: Boolean = true, // 是否格式化输出
val indentSpaces: Int = 2, // 缩进空格数
val excludeClasses: Set<KClass<*>> = setOf(), // 排除的类(避免循环引用)
val includePrivateFields: Boolean = false // 是否包含私有字段
)
// 默认配置
private val defaultConfig = DebugConfig()
/**
* 将任意对象转换为格式化的 JSON 字符串(用于调试)
*/
inline fun <reified T : Any> T.toDebugJson(
config: DebugConfig = defaultConfig
): String {
return buildDebugJson(this, config, 0, mutableSetOf())
}
/**
* 打印对象的调试信息
*/
inline fun <reified T : Any> T.printDebugJson(
tag: String? = null,
config: DebugConfig = defaultConfig
) {
val jsonString = toDebugJson(config)
val prefix = tag?.let { "[$it] " } ?: ""
println("$prefix${this::class.simpleName ?: "Unknown"}: $jsonString")
}
/**
* 批量打印列表中的对象
*/
inline fun <reified T : Any> Collection<T>.printAllDebugJson(
tag: String? = null,
config: DebugConfig = defaultConfig.copy(maxCollectionSize = 10) // 限制批量打印数量
) {
val prefix = tag?.let { "[$it] " } ?: ""
take(config.maxCollectionSize).forEachIndexed { index, item ->
val itemJson = item.toDebugJson(config)
println("${prefix}${item::class.simpleName}[$index]: $itemJson")
}
if (size > config.maxCollectionSize) {
println("${prefix}... and ${size - config.maxCollectionSize} more items")
}
}
// 内部实现
private fun buildDebugJson(
obj: Any?,
config: DebugConfig,
currentDepth: Int,
seenObjects: MutableSet<Int>
): String {
return when {
obj == null -> "null"
currentDepth >= config.maxDepth -> ""<max depth reached>""
isPrimitiveType(obj) -> formatPrimitive(obj, config)
isString(obj) -> formatString(obj.toString(), config)
isCollection(obj) -> formatCollection(obj, config, currentDepth, seenObjects)
isMap(obj) -> formatMap(obj, config, currentDepth, seenObjects)
isArray(obj) -> formatArray(obj, config, currentDepth, seenObjects)
else -> formatObject(obj, config, currentDepth, seenObjects)
}
}
private fun isPrimitiveType(obj: Any): Boolean {
return obj is Number || obj is Boolean || obj is Char
}
private fun isString(obj: Any): Boolean {
return obj is String
}
private fun isCollection(obj: Any): Boolean {
return obj is Collection<*>
}
private fun isMap(obj: Any): Boolean {
return obj is Map<*, *>
}
private fun isArray(obj: Any): Boolean {
return obj::class.java.isArray
}
private fun formatPrimitive(obj: Any, config: DebugConfig): String {
return when (obj) {
is String -> formatString(obj, config) // 这个应该不会走到,但保留安全
is Char -> ""$obj""
else -> obj.toString()
}
}
private fun formatString(str: String, config: DebugConfig): String {
val truncated = if (str.length > config.maxStringLength) {
str.substring(0, config.maxStringLength) + "..."
} else {
str
}
// 转义特殊字符
val escaped = truncated
.replace("\", "\\")
.replace(""", "\"")
.replace("\n", "\n")
.replace("\r", "\r")
.replace("\t", "\t")
return ""$escaped""
}
private fun formatCollection(
collection: Any,
config: DebugConfig,
currentDepth: Int,
seenObjects: MutableSet<Int>
): String {
val col = collection as Collection<*>
if (col.isEmpty()) return "[]"
val sizeInfo = if (config.showSizeForCollections) " // size=${col.size}" else ""
val items = col.take(config.maxCollectionSize).joinToString(",") { item ->
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
val itemJson = buildDebugJson(item, config, currentDepth + 1, seenObjects)
if (config.prettyPrint) "$indent$itemJson" else itemJson
}
val remaining = if (col.size > config.maxCollectionSize) {
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
"${if (config.prettyPrint) "," else ""}$indent"... and ${col.size - config.maxCollectionSize} more""
} else ""
val endIndent = if (config.prettyPrint) "\n${" ".repeat(currentDepth * config.indentSpaces)}" else ""
return "[$items$remaining$endIndent]$sizeInfo"
}
private fun formatMap(
map: Any,
config: DebugConfig,
currentDepth: Int,
seenObjects: MutableSet<Int>
): String {
val m = map as Map<*, *>
if (m.isEmpty()) return "{}"
val sizeInfo = if (config.showSizeForCollections) " // size=${m.size}" else ""
val entries = m.entries.take(config.maxCollectionSize).joinToString(",") { (key, value) ->
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
val keyJson = buildDebugJson(key, config, currentDepth + 1, seenObjects)
val valueJson = buildDebugJson(value, config, currentDepth + 1, seenObjects)
if (config.prettyPrint) "$indent$keyJson: $valueJson" else "$keyJson:$valueJson"
}
val remaining = if (m.size > config.maxCollectionSize) {
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
"${if (config.prettyPrint) "," else ""}$indent"...": "${m.size - config.maxCollectionSize} more entries""
} else ""
val endIndent = if (config.prettyPrint) "\n${" ".repeat(currentDepth * config.indentSpaces)}" else ""
return "{$entries$remaining$endIndent}$sizeInfo"
}
private fun formatArray(
array: Any,
config: DebugConfig,
currentDepth: Int,
seenObjects: MutableSet<Int>
): String {
val size = java.lang.reflect.Array.getLength(array)
if (size == 0) return "[]"
val items = (0 until minOf(size, config.maxCollectionSize)).joinToString(",") { index ->
val item = java.lang.reflect.Array.get(array, index)
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
val itemJson = buildDebugJson(item, config, currentDepth + 1, seenObjects)
if (config.prettyPrint) "$indent$itemJson" else itemJson
}
val remaining = if (size > config.maxCollectionSize) {
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
"${if (config.prettyPrint) "," else ""}$indent"... and ${size - config.maxCollectionSize} more""
} else ""
val endIndent = if (config.prettyPrint) "\n${" ".repeat(currentDepth * config.indentSpaces)}" else ""
return "[$items$remaining$endIndent]"
}
private fun formatObject(
obj: Any,
config: DebugConfig,
currentDepth: Int,
seenObjects: MutableSet<Int>
): String {
// 防止循环引用
val identityHashCode = System.identityHashCode(obj)
if (identityHashCode in seenObjects) {
return ""<cyclic reference to ${obj::class.simpleName}>""
}
seenObjects.add(identityHashCode)
// 检查排除的类
if (obj::class in config.excludeClasses) {
return ""<excluded class: ${obj::class.simpleName}>""
}
val properties = try {
obj::class.memberProperties
.filter { config.includePrivateFields || it.visibility != null }
.mapNotNull { prop ->
try {
// 确保可以访问私有属性
if (!prop.isAccessible) {
prop.isAccessible = true
}
prop to prop.get(obj)
} catch (e: Exception) {
null // 忽略无法访问的属性
}
}
.filter { (_, value) -> config.showNullValues || value != null }
} catch (e: Exception) {
emptyList() // 如果反射失败,返回空列表
}
if (properties.isEmpty()) {
return "{}"
}
val propsString = properties.joinToString(",") { (prop, value) ->
val indent = if (config.prettyPrint) "\n${" ".repeat((currentDepth + 1) * config.indentSpaces)}" else " "
val valueJson = buildDebugJson(value, config, currentDepth + 1, seenObjects)
if (config.prettyPrint) "$indent"${prop.name}": $valueJson" else ""${prop.name}":$valueJson"
}
val endIndent = if (config.prettyPrint) "\n${" ".repeat(currentDepth * config.indentSpaces)}" else ""
return "{$propsString$endIndent}"
}
// 便捷的配置构建器
fun debugConfig(block: DebugConfigBuilder.() -> Unit): DebugConfig {
val builder = DebugConfigBuilder()
builder.block()
return builder.build()
}
class DebugConfigBuilder {
var maxDepth: Int = 10
var maxCollectionSize: Int = 100
var maxStringLength: Int = 200
var showNullValues: Boolean = true
var showSizeForCollections: Boolean = true
var prettyPrint: Boolean = true
var indentSpaces: Int = 2
var excludeClasses: MutableSet<KClass<*>> = mutableSetOf()
var includePrivateFields: Boolean = false
fun build(): DebugConfig {
return DebugConfig(
maxDepth = maxDepth,
maxCollectionSize = maxCollectionSize,
maxStringLength = maxStringLength,
showNullValues = showNullValues,
showSizeForCollections = showSizeForCollections,
prettyPrint = prettyPrint,
indentSpaces = indentSpaces,
excludeClasses = excludeClasses,
includePrivateFields = includePrivateFields
)
}
}
}
// 扩展函数,便于使用
inline fun <reified T : Any> T.debugJson(): String = DebugUtils.toDebugJson(this)
inline fun <reified T : Any> T.printDebug(tag: String? = null) =
DebugUtils.printDebugJson(this, tag)
inline fun <reified T : Any> Collection<T>.printAllDebug(tag: String? = null) =
DebugUtils.printAllDebugJson(this, tag)
使用示例
kotlin
// 示例数据类(假设这些是第三方库的类,不能修改)
data class User(
val id: Int,
val name: String,
private val password: String, // 私有字段
val email: String? = null, // 可空字段
val profile: Profile,
val tags: List<String> = emptyList(),
val preferences: Map<String, Any> = emptyMap()
)
data class Profile(
val age: Int,
val address: Address,
val friends: List<User> = emptyList() // 循环引用测试
)
data class Address(
val street: String,
val city: String,
val coordinates: DoubleArray
)
// 使用示例
fun main() {
val address = Address("Main St", "New York", doubleArrayOf(40.7128, -74.0060))
val profile = Profile(30, address)
val user = User(
id = 1,
name = "John Doe",
password = "secret",
email = "john@example.com",
profile = profile,
tags = listOf("android", "kotlin", "kmp"),
preferences = mapOf(
"theme" to "dark",
"notifications" to true,
"language" to "en"
)
)
// 基本使用
user.printDebug()
// 带标签打印
user.printDebug("UserService")
// 自定义配置
val customConfig = DebugUtils.debugConfig {
maxDepth = 5
maxCollectionSize = 50
showNullValues = false
includePrivateFields = true // 显示私有字段
}
val json = user.toDebugJson(customConfig)
println("Custom: $json")
// 批量打印
val users = listOf(user, user, user)
users.printAllDebug("UserList")
// 处理复杂嵌套和循环引用
val complexConfig = DebugUtils.debugConfig {
maxDepth = 3
excludeClasses = setOf(User::class) // 避免循环引用
}
user.printDebug(complexConfig)
}
主要特性
- 深度嵌套处理 - 支持任意深度的对象嵌套
- 循环引用检测 - 自动检测并避免无限递归
- 性能控制 - 可配置最大深度和集合大小
- 私有字段访问 - 可选是否显示私有字段
- 格式化输出 - 可读性强的缩进格式
- 类型安全 - 完整的类型处理
- 错误处理 - 优雅处理反射异常
- 批量操作 - 支持集合的批量打印
这个工具非常适合调试复杂的第三方数据结构,无需修改原始类即可获得详细的 JSON 格式输出。