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

Moshi解析JSON:如何将空字符串转为数值类型Null(避免重复适配器)

解决Moshi解析空字符串到可空数值类型的通用适配器方案

当使用Moshi解析包含空字符串(如{"appId":"1", "clicks":""})的JSON到带有可空Long/Int等数值字段的Kotlin数据类时,会因类型不匹配抛出Expected a long but was at path $.clicks这类错误。单独为每个数值类型编写适配器会产生大量重复代码,以下是更优的通用解决方案:

通用空字符串转Null数值适配器(基于JsonAdapter.Factory)

通过实现Moshi的JsonAdapter.Factory,可以一次性为所有数值类型(Long、Int、Short、Float、Double等)处理空字符串转null的逻辑,无需重复编写适配器:

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.lang.reflect.Type

class EmptyStringToNullNumberAdapterFactory : JsonAdapter.Factory {
    override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
        // 仅处理可空的数值类型(Long?、Int?等)
        val rawType = Types.getRawType(type)
        if (!Number::class.java.isAssignableFrom(rawType) || !Types.isNullable(type)) {
            return null
        }

        val delegate = moshi.nextAdapter<Number>(this, type, annotations)
        return object : JsonAdapter<Number?>() {
            override fun fromJson(reader: JsonReader): Number? {
                return if (reader.peek() == JsonReader.Token.STRING) {
                    val stringValue = reader.nextString()
                    // 空字符串转null,非空则尝试转换为对应数值类型
                    if (stringValue.isBlank()) null else convertToNumber(stringValue, rawType)
                } else {
                    // 非字符串类型交给默认适配器处理
                    delegate.fromJson(reader)
                }
            }

            override fun toJson(writer: JsonWriter, value: Number?) {
                // 序列化时直接用默认逻辑
                delegate.toJson(writer, value)
            }

            private fun convertToNumber(value: String, targetType: Class<*>): Number? {
                return try {
                    when (targetType) {
                        Long::class.java -> value.toLong()
                        Int::class.java -> value.toInt()
                        Short::class.java -> value.toShort()
                        Float::class.java -> value.toFloat()
                        Double::class.java -> value.toDouble()
                        Byte::class.java -> value.toByte()
                        else -> null
                    }
                } catch (e: NumberFormatException) {
                    // 转换失败返回null
                    null
                }
            }
        }
    }
}

注册适配器到Moshi实例

创建Moshi实例时,将上述工厂类添加进去即可生效:

val moshi = Moshi.Builder()
    .add(EmptyStringToNullNumberAdapterFactory())
    .build()

// 测试解析
val json = """{"appId":"1", "clicks":"", "count":"123"}"""
val adjustAdapter = moshi.adapter(Adjust::class.java)
val adjust = adjustAdapter.fromJson(json)
// 此时adjust.clicks为null,adjust.count为123,符合预期

方案优势

  • 一次性覆盖所有数值类型,无需为Long、Int等单独编写适配器
  • 自动处理可空字段,非可空数值类型不受影响(若JSON传空字符串到非可空字段,仍会抛出错误,符合预期)
  • 支持转换失败时返回null,避免因格式错误的字符串导致解析崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:05:25