Gson-解析带有包含空/null值的字段的jsonString(在Kotlin中)



TL;DR

对于包含...,field=,...的json字符串,Gson不断抛出JsonSyntaxException。我能做什么?

案例

我必须与第三个api通信,它倾向于提供这样的数据:

{
"fieldA": "stringData",
"fieldB": "",
"fieldC": ""
}

然而,在我的应用程序项目中,结果是这样的:

val jsonString = "{fieldA=stringData,fieldB=,fieldC=}"

问题

我尝试使用标准方法对其进行反序列化:

val jsonString = "{fieldA=stringData,fieldB=,fieldC=}"
val parseJson = Gson().fromJson(jsonString, JsonObject::class.java)
assertEquals(3, parseJson.size())

但它导致了一个例外:

com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unexpected value at line 1 column 28 path $.fieldB

无效的解决方案

我尝试了很多解决方案,但都不起作用。其中:

  • 设置自定义数据类并将值设置为null
data class DataExample(
val fieldA: String?,
val fieldB: String?,
val fieldC: String?,
)
val parseToObject = Gson().fromJson(jsonString, DataExample::class.java)
  • 改为使用JsonElement
data class DataExample(
val fieldA: JsonElement,
val fieldB: JsonElement,
val fieldC: JsonElement,
)
val parseToObject = Gson().fromJson(jsonString, DataExample::class.java)
  • 应用反序列化程序:
class EmptyToNullDeserializer<T>: JsonDeserializer<T> {
override fun deserialize(
json: JsonElement, typeOfT: Type, context: JsonDeserializationContext
): T? {
if (json.isJsonPrimitive) {
json.asJsonPrimitive.also {
if (it.isString && it.asString.isEmpty()) return null
}
}
return context.deserialize(json, typeOfT)
}
}
data class DataExample(
@JsonAdapter(EmptyToNullDeserializer::class)
val fieldA: String?,
@JsonAdapter(EmptyToNullDeserializer::class)
val fieldB: String?,
@JsonAdapter(EmptyToNullDeserializer::class)
val fieldC: String?,
)
val parseToObject = Gson().fromJson(jsonString, DataExample::class.java)

或在GsonBuilder:中使用

val gson = GsonBuilder()
.registerTypeAdapter(DataExample::class.java, EmptyToNullDeserializer<String>())
.create()
val parseToObject = gson.fromJson(jsonString, DataExample::class.java)

我还能做什么?

它不是一个有效的JSON。你需要自己解析它。这个字符串可能是使用Map::toString()方法生成的。以下是将其解析为Map<字符串,字符串>

val jsonString = "{fieldA=stringData,fieldB=,fieldC=}"
val userFieldsMap = jsonString.removeSurrounding("{", "}").split(",") // split by ","
.mapNotNull { fieldString ->
val keyVal = fieldString.split("=")
// check if array contains exactly 2 items
if (keyVal.size == 2) {
keyVal[0].trim() to keyVal[1].trim()  // return@mapNotNull
} else {
null // return@mapNotNull
}
}
.toMap()

事实证明,就像@frc129和许多其他人所说的那样,它不是一个有效的JSON。

然而,事实是,Gson处理的情况比JSON应该处理的要多,比如下面的数据:

val jsonString = "{fieldA=stringData,fieldB=s2,fieldC=s3}"
val parseJson = Gson().fromJson(jsonString, JsonObject::class.java)
// This will NOT throw exception, even the jsonString here is not actually a JSON string.
assertEquals(3, parseJson.size())
assertEquals("stringData", parseJson["fieldA"].asString)
assertEquals("s2", parseJson["fieldB"].asString)
assertEquals("s3", parseJson["fieldC"].asString)

进一步的研究表明,这里和问题中提到的字符串更像是Map到字符串。

我对GSON处理Map有点误解。这应该被视为一种额外方便的支持,但不是一种法律程序。简而言之,它不应该被转换,并且数据格式应该是固定的。然后我将着手进行服务器和基础的转换。

在这里留个便条就行了。如果将来有人想快速修复字符串,你可以看看@frc129的答案;然而,这方面的理想解决方案是固定数据提供者;正确的JSON格式":

val jsonString = "{"fieldA":"stringData","fieldB":"","fieldC":""}"
val parseJson = Gson().fromJson(jsonString, JsonObject::class.java)
assertEquals(3, parseJson.size())
assertEquals("stringData", parseJson["fieldA"].asString)
assertEquals("", parseJson["fieldB"].asString)
assertEquals("", parseJson["fieldC"].asString)

最新更新