Json 不向Scala枚举添加参数构造函数

Json 不向Scala枚举添加参数构造函数,json,scala,enums,gson,Json,Scala,Enums,Gson,我有以下Scala枚举: object RunMode extends Enumeration { val CLIENT_MODE = Value("CLIENT") val SERVER_MODE = Value("SERVER") } 我的应用程序接收了一些JSON作为输入,例如: { "version" : "0.1", "runMode" : "CLIENT" } 这里的JSON字段“runMode”实际上是我的runModeenum,它的值总是“CLIENT

我有以下Scala枚举:

object RunMode extends Enumeration {
  val CLIENT_MODE = Value("CLIENT")
  val SERVER_MODE = Value("SERVER")
}
我的应用程序接收了一些JSON作为输入,例如:

{
    "version" : "0.1",
    "runMode" : "CLIENT"
}
这里的JSON字段“
runMode
”实际上是我的
runMode
enum,它的值总是“CLIENT”或“SERVER”。我正在尝试使用GSON将此JSON反序列化为
AppConfig
实例:

class AppConfig(version : String, runMode : RunMode) {
  def version() : String = { this.version }
  def runMode() : RunMode.Value = { this.runMode }
}  
我有以下GSON代码:

val gson = new Gson()
val text = Source.fromFile(jsonConfigFile).mkString
gson.fromJson(text, classOf[AppConfig])
运行时:

java.lang.RuntimeException: Unable to invoke no-args constructor for class scala.Enumeration$Value. Register an InstanceCreator with Gson for this type may fix this problem.
> Buildiat com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
    at com.google.gson.Gson.fromJson(Gson.java:887)
  <rest of stacktrace omitted for brevity>
java.lang.RuntimeException:无法为类scala.Enumeration$Value调用任何参数构造函数。为此类型向Gson注册InstanceCreator可能会解决此问题。
>Buildiat com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
位于com.google.gson.internal.bind.ReflectTypeAdapterFactory$Adapter.read(ReflectTypeAdapterFactory.java:210)
位于com.google.gson.internal.bind.ReflectTypeAdapterFactory$1.read(ReflectTypeAdapterFactory.java:129)
位于com.google.gson.internal.bind.ReflectTypeAdapterFactory$Adapter.read(ReflectTypeAdapterFactory.java:220)
位于com.google.gson.gson.fromJson(gson.java:887)
很明显,GSON期望
RunMode
有一个无参数构造函数,但它没有,因此无法在运行时反序列化我的JSON文件


我尝试了一百万种不同的组合,但似乎找不到神奇的构造函数定义。所以我问:我如何向
运行模式添加一个无参数构造函数,以便GSON可以将其反序列化为
AppConfig
实例?

这并不能直接回答为什么使用GSON会失败,但提供了一种替代方法。下面是一个示例,使用:

运行模式
枚举定义:

object RunMode extends Enumeration {
  type RunMode = Value
  val CLIENT_MODE = Value("CLIENT")
  val SERVER_MODE = Value("SERVER")

  implicit def runModeCodec: CodecJson[RunMode.RunMode] = CodecJson({
    case CLIENT_MODE => "CLIENT".asJson
    case SERVER_MODE => "SERVER".asJson
  }, c => c.focus.string match {
    case Some("CLIENT") => DecodeResult.ok(CLIENT_MODE)
    case Some("SERVER") => DecodeResult.ok(SERVER_MODE)
    case _ => DecodeResult.fail("Could not decode RunMode", c.history)
  })
}
Foo
的定义(与要创建的对象匹配):

现在是解码/编码示例:

object ArgonautEnumCodec {
  def main(args: Array[String]): Unit = {
    val res: String = Foo("0.1", RunMode.CLIENT_MODE).asJson.toString
    println(res)

    val foo: Foo = res.decodeOption[Foo].get
    println(foo)
  }
}
收益率:

{"version":"0.1","runMode":"CLIENT"}
Foo(0.1,CLIENT)

因为我不是Scala的人,但有一些Gson的背景,所以对Scala如何工作的一些见解对我来说很有趣。出现异常的原因是Gson无法实例化抽象类
scala.Enumeration.Value
AutoConfig
类内容与vanilla Java中的以下类非常相似:

final类AppConfig{
最终字符串版本;
//这就是ig失败的地方
最终scala.Enumeration.Value运行模式;
AppConfig(最终字符串版本,最终scala.Enumeration.Value运行模式){
this.version=版本;
this.runMode=runMode;
}
}
据我所知,Scala枚举是如何实现的,与Java枚举不同,它们本身没有类型,每个Scala枚举值似乎都是Scala.enumeration$Val的一个实例,没有从其类型中提供足够的“主机”枚举类型信息(但是实例似乎有它们的外部类引用)。这就是为什么自定义实现自定义类型适配器没有那么简单,并且需要对实际的枚举类型进行一些检查(但不确定如何实现它)

Gson提供了一个特殊的注释
@JsonAdapter
,它可以注释某个字段,包括要应用的类型适配器。因此,上面类中的
AppConfig.runMode
可以注释如下:

@JsonAdapter(RunModeEnumTypeAdapter.class)
最终scala.Enumeration.Value运行模式;
请注意,它的名称中有一些关于目标类型的提示。这是因为可能没有其他方法来指定目标枚举类型。现在,介绍如何实现泛型
scala.enumeration
type适配器

/E-绑定到Scala枚举的特殊泛型类型
//因此,任何Scala枚举都可以使用此类型适配器进行处理
抽象类AbstractScalaEnumTypeAdapter
扩展类型适配器{
私人最终E点算;
受保护的AbstractScaleNumTypeAdapter(最终E枚举){
this.enumeration=枚举;
}
@凌驾
@抑制警告(“资源”)
公共最终无效写入(最终JsonWriter out,最终scala.Enumeration.Value)
抛出IOException{
//如果给定的值为null,则必须将null写入写入器(但它取决于特定的Gson实例配置)
如果(值==null){
out.nullValue();
}否则{
//Scala是否提供了类似java.lang.Enumeration的名称?
out.value(value.toString());
}
}
@凌驾
公共最终scala.Enumeration.Value read(中的最终JsonReader)
抛出IOException{
最终JsonToken标记=in.peek();
交换机(令牌){
大小写为空:
//使用`null`JSON令牌
in.nextNull();
返回null;
大小写字符串:
//使用JSON字符串值并按其名称查找适当的Scala枚举值
最后一个字符串rawValue=in.nextString();
返回枚举.withName(rawValue);
//这些大小写标签是样式问题,涵盖了其余可能的Gson-JSON标记,实际上并不是必需的
案例开始\u数组:
案例结束单元阵列:
案例开始对象:
案例结束对象:
案例名称:
案件编号:
大小写布尔值:
案例结束文件:
抛出新的MalformedJsonException(“意外令牌:+令牌”);
//还有别的事吗?绝对不能发生
违约:
抛出新断言错误(令牌);
}
}
}
现在,
RunMode
可以绑定到上面的类型适配器:

final类RunModeEnumTypeAdapter
扩展AbstractScaleNumTypeAdapter{
//Gson可以自己实例化它
专用RunModeEnumTypeAdapter(){
//这是从Java的角度来看的
//这就是我上面提到的“暗示”
超级(运行模式$.MODULE$);
}
}
使用示例:

最终Gson g
{"version":"0.1","runMode":"CLIENT"}
Foo(0.1,CLIENT)