You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Multi Platform中Double长小数序列化格式异常问题

KMP中Double序列化出现科学计数法格式异常的解决办法

问题描述

在Kotlin Multi Platform(KMP)开发中,使用kotlinx.serialization序列化包含多位小数的Double类型时,出现格式异常:数值如0.0001会被序列化为1.0E-4这种科学计数法格式,而0.001及以上的数值则保持正常的小数格式。

示例输入(Map形式):

mapOf(
    "test" to listOf(
        0.1, 
        0.01,
        0.001,
        0.0001,
        0.00001,
        0.12345,
        0.123456789,
        0.123467890123,
        0.0000012345,
    )
)

原代码执行后异常输出:

{"test":[0.1,0.01,0.001,1.0E-4,1.0E-5,0.12345,0.123456789,0.123467890123,1.2345E-6]}

原因分析

kotlinx.serialization的默认序列化逻辑在KMP跨平台场景下,会对小于0.001的Double数值自动采用科学计数法,这是为了统一不同平台的Double字符串化行为,但不符合业务对固定小数格式的需求。同时KMP无法使用JVM专属的格式化工具(如DecimalFormat),需要采用跨平台可行的方案。

解决方案

通过自定义Double的格式化逻辑,将其转换为无科学计数法的字符串后再进行序列化,同时保证跨平台兼容性。

修改后的完整代码

import kotlinx.serialization.*
import kotlinx.serialization.json.*

// 跨平台Double格式化函数,避免科学计数法,清理多余末尾0
fun formatDouble(d: Double): String {
    return if (d.isNaN() || d.isInfinite()) {
        d.toString()
    } else {
        d.toBigDecimal()
            .toPlainString()
            .let { str ->
                // 去掉末尾的0和多余的小数点
                if (str.contains('.')) {
                    str.replace(Regex("0*$"), "").replace(Regex("\\.$"), "")
                } else {
                    str
                }
            }
    }
}

fun convertToJsonString(inputMap: Map<String, Any>): String {
    // 递归处理嵌套结构
    fun traverseMap(value: Any): JsonElement {
        return when (value) {
            is Map<*, *> -> {
                JsonObject(
                    value.filter { it.key is String }
                        .map { it as Map.Entry<String, Any> }
                        .associate { it.key to traverseMap(it.value) }
                )
            }
            is List<*> -> {
                JsonArray(value.map { traverseMap(it!!) })
            }
            is Double -> {
                // 对Double单独应用自定义格式化
                JsonPrimitive(formatDouble(value))
            }
            is Number, is String, is Boolean -> {
                JsonPrimitive(value.toString())
            }
            else -> {
                throw IllegalArgumentException("不支持的数值类型: ${value.javaClass.name}")
            }
        }
    }

    val processedJson = JsonObject(inputMap.mapValues { traverseMap(it.value) })
    return Json.encodeToString(processedJson)
}

fun main() {
    val map = mapOf(
        "test" to listOf(
            0.1,
            0.01,
            0.001,
            0.0001,
            0.00001,
            0.12345,
            0.123456789,
            0.123467890123,
            0.0000012345,
        )
    )

    val outputJsonString = convertToJsonString(map)
    println(outputJsonString)
}

关键修改点

  1. 新增跨平台格式化函数:利用BigDecimal.toPlainString()将Double转换为无科学计数法的字符串,同时清理末尾多余的0和小数点,保证格式整洁。
  2. 调整分支顺序:单独处理Double类型,确保自定义逻辑优先执行,避免被通用Number分支覆盖。
  3. 优化原逻辑:移除重复的Number分支,简化Map过滤代码。

执行结果

修改后输出的JSON所有数值均保持小数格式:

{"test":[0.1,0.01,0.001,0.0001,0.00001,0.12345,0.123456789,0.123467890123,0.0000012345]}

内容的提问来源于stack exchange,提问作者yarlg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 15:07:07