how to deserialize a json string that contains @@ with scala'

一曲冷凌霜 提交于 2021-01-20 08:59:37

问题


As the title already explains, I would like to deserialize a json string that contains a key that starts with @@. With the @@ my standard approach using case classes sadly does not work anymore.

val test = """{"@@key": "value"}"""
case class Test(@@key: String) // not possible
val gson =  new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])

How can work with the @@ withtout preprocessing the input json string?


回答1:


The simplest answer is to quote the field name:

case class Test(`@@key`: String)



回答2:


I experimented a bit but it seems that GSON doesn't interoperate well with Scala case classes (or the other way around, I guess it's a matter of perspective). I tried playing around with scala.beans.BeanProperty but it doesn't seem like it makes a difference.

A possible way to go is to use a regular class and the SerializedName annotation, as in this example:

import com.google.gson.{FieldNamingPolicy, GsonBuilder}
import com.google.gson.annotations.SerializedName

final class Test(k: String) {
  @SerializedName("@@key") val key = k
  override def toString(): String = s"Test($key)"
}

val test = """{"@@key": "foobar"}"""
val gson =  new GsonBuilder().create()
val res = gson.fromJson(test, classOf[Test])
println(res)

You can play around with this code here on Scastie.

You can read more on SerializedName (as well as other naming-related GSON features) here on the user guide.




回答3:


I'm not a Scala programmer, I just used javap and reflection to check what the Scala compiler generated and slightly "learnt" how some Scala internals work. It does not work for you because of several reasons:

  • The Scala compiler puts case class elements annotations to the constructor parameters, whereas Gson @SerializedName can only work with fields and methods:
// does not work as expected
case class Test(@SerializedName("@@key") `@@key`: String)

From the plain Java perspective:

final Constructor<Test> constructor = Test.class.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(Arrays.deepToString(constructor.getParameterAnnotations()));

public Test(java.lang.String)
[[@com.google.gson.annotations.SerializedName(alternate=[], value=@@key)]]

Not sure why the Scala compiler does not replicate the annotations directly to the fields, but the Java language does not allow annotating parameters with the @SerializedName annotation causing a compilation error (JVM does not treats it as a failure either).

  • The field name is actually encoded in the class file.

From the Java perspective:

final Field field = Test.class.getDeclaredField("$at$atkey"); // the real name of the `@@key` element
System.out.println(field);
System.out.println(Arrays.deepToString(field.getDeclaredAnnotations()));

private final java.lang.String Test.$at$atkey <- this is how the field can be accessed from Java
[] <- no annotations by default

  • Scala allows moving annotations to fields and this would make your code work accordingly to how Gson @SerializedName is designed (of course, no Scala in mind):
import scala.annotation.meta.field
...
case class Test(@(SerializedName@field)("@@key") `@@key`: String)

Test(value)


If for some/any reason you must use Gson and can't annotate each field with @SerializedName, then you can implement a custom type adapter, but I'm afraid that you have to have deep knowledge in how Scala works. If I understand what Scala does, it annotates every generated class with the @ScalaSignature annotation. The annotation provides the bytes() method that returns a payload that's most likely can be used to detect whether the annotated type is a case class, and probably how its members are declared. I didn't find such a parser/decoder, but if you find one, you can do the following in Gson:

  • register a type adapter factory that checks whether it can handle it (basically, analyzing the @ScalaSignature annotation, I believe);
  • if it can, then create a type adapter that is aware of all case class fields, their names possibly handling the @SerializedName yourself, as you can neither extend Gson ReflectiveTypeAdapterFactory nor inject a name remapping strategy;
  • take transient fields (for good) and other exclusion strategies (for completeness) into account;
  • read/write each non-excluded field.

Too much work, right? So I see two easy options here: either use a Scala-aware JSON tool like other people are suggesting, or annotate each field that have such a special name.



来源:https://stackoverflow.com/questions/62259410/how-to-deserialize-a-json-string-that-contains-with-scala

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!