我有以下Scala枚举:
object RunMode extends Enumeration {
val CLIENT_MODE = Value("CLIENT")
val SERVER_MODE = Value("SERVER")
}
我有一些JSON我的应用程序作为输入接受,例如:
{
"version" : "0.1",
"runMode" : "CLIENT"
}
此处JSON字段“runMode
”实际上是我的RunMode
枚举,其值始终为“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>
很明显,GSON希望RunMode
有一个no-arg构造函数,但它没有,因此它无法在运行时反序列化我的JSON文件。
我已经尝试了一百万个不同的组合,但似乎无法找到神奇的构造函数定义。所以我问:如何向RunMode
添加无参数构造函数,以便GSON可以将其反序列化为AppConfig
实例?
答案 0 :(得分:0)
这并没有直接回答为什么使用Gson失败,但提供了另一种选择。以下是使用argonaut的示例:
RunMode
枚举定义:
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
的定义(匹配您要创建的对象):
case class Foo(version: String, runMode: RunMode)
object Foo {
implicit def codec: CodecJson[Foo] =
casecodec2(Foo.apply, Foo.unapply)("version", "runMode")
}
现在解码/编码示例:
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)
答案 1 :(得分:0)
由于我不是Scala人,但有一些Gson背景,偷看Scala如何工作的一些见解对我来说很有趣。您获得异常的原因是Gson无法实例化抽象类scala.Enumeration.Value
。 AutoConfig
类内容与vanilla Java中的以下类非常相似:
final class AppConfig {
final String version;
// This is where ig gets failed
final scala.Enumeration.Value runMode;
AppConfig(final String version, final scala.Enumeration.Value runMode) {
this.version = version;
this.runMode = runMode;
}
}
据我所知,Scala枚举是如何实现的,与Java枚举不同,它们本身没有类型,并且每个Scala枚举值似乎都是scala.Enumeration$Val
的实例,没有提供足够的“主机”枚举从其类型中键入信息(但实例似乎有其外部类引用)。这就是为什么定制实现自定义类型适配器并不那么简单,需要对真实的枚举类型进行一些检查(不知道如何实现它)。
Gson提供了一个特殊的注释@JsonAdapter
,可以注释某个字段,包括要应用的类型适配器。因此,上述类中的AppConfig.runMode
可以注释为:
@JsonAdapter(RunModeEnumTypeAdapter.class)
final scala.Enumeration.Value runMode;
请注意,它在名称中对目标类型有一些提示。这是因为可能没有其他方法来指定目标枚举类型。现在,如何实现通用的scala.Enumeration
类型适配器。
// E - a special generic type bound to associate a Scala enumeration with
// So any Scala enumeration can be processed with this type adapter
abstract class AbstractScalaEnumTypeAdapter<E extends scala.Enumeration>
extends TypeAdapter<scala.Enumeration.Value> {
private final E enumeration;
protected AbstractScalaEnumTypeAdapter(final E enumeration) {
this.enumeration = enumeration;
}
@Override
@SuppressWarnings("resource")
public final void write(final JsonWriter out, final scala.Enumeration.Value value)
throws IOException {
// If the given value is null, null must be written to the writer (however it depends on a particular Gson instance configuration)
if ( value == null ) {
out.nullValue();
} else {
// Does Scala provide something like java.lang.Enumeration#name?
out.value(value.toString());
}
}
@Override
public final scala.Enumeration.Value read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case NULL:
// Consume the `null` JSON token
in.nextNull();
return null;
case STRING:
// Consume a JSON string value and lookup an appropriate Scala enumeration value by its name
final String rawValue = in.nextString();
return enumeration.withName(rawValue);
// These case labels are matter of style and cover the rest of possible Gson JSON tokens, and are not really necessary
case BEGIN_ARRAY:
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
// Something else? Must never happen
default:
throw new AssertionError(token);
}
}
}
现在,RunMode
可以绑定到上面的类型适配器:
final class RunModeEnumTypeAdapter
extends AbstractScalaEnumTypeAdapter<RunMode$> {
// Gson can instantiate this itself
private RunModeEnumTypeAdapter() {
// This is how it looks like from the Java perspective
// And this is the "hint" I was talking about above
super(RunMode$.MODULE$);
}
}
使用示例:
final Gson gson = new Gson();
final AppConfig appConfig = gson.fromJson("{\"version\":\"0.1\",\"runMode\":\"CLIENT\"}", AppConfig.class);
System.out.println(appConfig.version);
System.out.println(appConfig.runMode);
System.out.println(gson.toJson(appConfig));
输出:
0.1
CLIENT
{“version”:“0.1”,“runMode”:“CLIENT”}
可能不像Scala那样漂亮和紧凑,但我希望上面的代码可以毫无问题地翻译成Scala。