-1

TL;DR

For a json string containing ...,field=,..., Gson keeps throwing JsonSyntaxException. What can I do?

The Case

I have to communicate with a 3rd api, Which tends to provide data like this:

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

However, In my app project, it turns out to read like this:

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

The Problem

I tried using the standard method to deserialize it:

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

But it results in a Exception:

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

The Solutions That Don't Work

I have tried so many solutions, none of them works. Including:

  • Setup a custom data class and set value to nullable
data class DataExample(
    val fieldA: String?,
    val fieldB: String?,
    val fieldC: String?,
)
val parseToObject = Gson().fromJson(jsonString, DataExample::class.java)
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)

or using it in GsonBuilder:

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

What else can I do?

Samuel T. Chou
  • 521
  • 6
  • 31
  • 1
    How did you get that string `"{fieldA=stringData,fieldB=,fieldC=}"`? that simply isn't valid JSON. What do you mean with " it turns out to read like this:". you must have done some kind of transformation to it – Ivo Mar 18 '22 at 11:05
  • 1
    The problem doesn't lie in converting that string. But getting the right string. The response that you say "provide data like this:", that is the actual jsonString that you should be working with. You somehow transformed the response – Ivo Mar 18 '22 at 11:19
  • 1
    Your `jsonString` is not a JSON, but most likely a toString result looking like a JSON. You can't parse it as JSON obviously. Did you try asking your server maintainers to fix their endpoints? – terrorrussia-keeps-killing Mar 18 '22 at 14:08
  • Yeah, it turns out that the API given was not a valid JSON at all. Sorry for my misunderstanding.. – Samuel T. Chou Mar 21 '22 at 02:36

2 Answers2

1

It is not a valid JSON. You need to parse it by yourself. Probably this string is made by using Map::toString() method. Here is the code to parse it into Map<String, String>

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()
Kudratillo
  • 190
  • 11
0

It turns out that, like @frc129 and many others said, it is not an valid JSON.

The truth is however, Gson handles more situation than JSON should be, like the data below:

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)

Further investigation indicates that -- the string mentioned here and in the question -- is more like a Map to string.

I got a bit misunderstanding with GSON dealing with Map. That should be treat as a extra handy support, but not a legal procedure. In short, it is not supposed to be transformed, and data format should be fixed. I'll go work with server and base transformation then.

Just leave a note here. If someone in the future want some quick fix to string, you may take a look at @frc129 answer; however, the ideal solution to this is to fix the data provider to provide "the correct JSON format":

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)
Samuel T. Chou
  • 521
  • 6
  • 31