带Scala的Gson会导致枚举堆栈溢出

带Scala的Gson会导致枚举堆栈溢出,scala,gson,stack-overflow,Scala,Gson,Stack Overflow,我在Scala类中定义了一个枚举,如下所示 // define compression types as enumerator object CompressionType extends Enumeration { type CompressionType = Value val None, Gzip, Snappy, Lz4, Zstd = Value } 我有一个我想用JSON序列化的类 case class ProducerConfig(b

我在Scala类中定义了一个枚举,如下所示

// define compression types as enumerator
  object CompressionType extends Enumeration
  {
    type CompressionType = Value
    
    val None, Gzip, Snappy, Lz4, Zstd = Value    
  }
我有一个我想用JSON序列化的类

case class ProducerConfig(batchNumMessages : Int, lingerMs : Int, messageSize : Int,
                            topic: String, compressionType: CompressionType.Value )
该类包括枚举对象。似乎使用GSON序列化会由于某种循环依赖性而导致堆栈溢出

val gson = new Gson
      val jsonBody = gson.toJson(producerConfig)
      println(jsonBody)
下面是我得到的堆栈跟踪。我看到了这一点,但解决方案似乎是Java解决方案,不适用于scala。有人能澄清一下吗

17:10:04.475 [ERROR] i.g.a.Gatling$ - Run crashed
java.lang.StackOverflowError: null
        at com.google.gson.stream.JsonWriter.beforeName(JsonWriter.java:617)
        at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:400)
        at com.google.gson.stream.JsonWriter.value(JsonWriter.java:526)
        at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:233)
        at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:218)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)

我不是Scala的人,但我认为Gson在这里使用是错误的工具

  • 首先,Gson不知道scala.Enumeration,因此将其作为常规数据包处理,可以使用反射进行遍历
  • 其次,没有一种简单(如果有的话?)的方法可以反序列化到原始值状态(如果您只想生成而不是消费JSON文档,则可以忽略)
原因如下:

对象单一
扩展枚举{
仅val=值
}
final类内部构件{
私有内部构件(){
}
静态void inspect(最终对象o、最终排除器、最终布尔序列化)
抛出非法访问异常{
检查(o,clazz->!excluder.excludeClass(clazz,serialize),field->!excluder.excludeField(field,serialize));
}
静态void inspect(最终对象o,最终谓词>inspectClass,最终谓词c=o.getClass();c!=null;c=c.getSuperclass()){
如果(!检查类测试(c)){
继续;
}
系统输出打印ln(c);
for(最终字段f:c.getDeclaredFields()){
如果(!检查字段测试(f)){
继续;
}
f、 setAccessible(true);
System.out.printf(“\t%s:%s\n”,f,f.get(o));
}
}
}
}
final Object value=Single.Only();
检查内部构件(值,gson.excluder(),真);
产生:

class scala.Enumeration$Val
    private final int scala.Enumeration$Val.i: 0
    private final java.lang.String scala.Enumeration$Val.name: null
class scala.Enumeration$Value
    private final scala.Enumeration scala.Enumeration$Value.scala$Enumeration$$outerEnum: Single
class java.lang.Object
如您所见,有两个关键领域:

  • private final java.lang.String scala.Enumeration$Val.name
    给出null,除非命名(尽管可以使用
    toString
    获得枚举元素)
  • private final scala.Enumeration scala.Enumeration$Value.scala$Enumeration$$outerEnum
    实际上是对具体枚举外部类的引用(这实际上是无限递归的原因,因此是堆栈溢出错误)
这两种方法阻止了正确的反序列化。 外部枚举类型至少可以通过三种方式获得:

  • 或者为可以包含此类枚举的所有类型实现自定义类型适配器(对于数据包(Scala中的case类?)非常容易,因为字段已经包含类型信息,尽管Gson对此提供了较差的支持;对于上述或集合这样的单基元文本不起作用)
  • 或者将外部枚举名称烘焙为JSON,其中包含名称和外部类型的两个条目
后者可以这样做(在Java中,希望在Scala中很容易简化):

final类ScalaStuff{
私有静态最终字段outerEnumField;
私有静态最终映射withNameMethodCache=new ConcurrentHashMap();
静止的{
试一试{
outerEnumField=Enumeration.Value.class.getDeclaredField(“scala$Enumeration$$outerEnum”);
outerEnumField.setAccessible(true);
}捕获(最终NoSuchFieldException ex){
抛出新的运行时异常(ex);
}
}
私有ScalaStuff(){
}
@非空
静态字符串toEnumerationName(@Nonnull final Enumeration.Value){
试一试{
最终类enumerationClass=Class.forName(类型);
withNameMethod=enumerationClass.getMethod(“withName”,String.class);
withNameMethodCache.put(类型,withNameMethod);
}
试一试{
return(Enumeration.Value)with nameMethod.invoke(null,enumerationName);
}捕获(最终IllegaAccessException | InvocationTargetException ex){
抛出新的运行时异常(ex);
}
}
}
final类ScalaEnumerationTypeAdapterFactory
实现TypeAdapterFactory{
私有静态最终TypeAdapterFactory实例=新的ScalaEnumerationTypeAdapterFactory();
专用scaleaNumerationTypeAdapterFactory()的{
}
静态TypeAdapterFactory getInstance(){
返回实例;
}
@凌驾
@可空
公共类型适配器创建(最终Gson Gson、最终TypeToken TypeToken){
如果(!Enumeration.Value.class.isAssignableFrom(typeToken.getRawType())){
返回null;
}
@抑制警告(“未选中”)
final TypeAdapter TypeAdapter=(TypeAdapter)Adapter.instance;
返回类型适配器;
}
专用静态最终类适配器
扩展类型适配器{
私有静态最终类型适配器实例=新适配器()
.nullSafe();
专用适配器(){
}
@凌驾
public void write(最终JsonWriter out,最终枚举.Value)
抛出IOException{
out.beginObject();
名称(“类型”);
out.value(ScalaStuff.toEnumerationName(value));
名称(“名称”);
out.value(value.toString());
out.endObject();
}
@凌驾
公共枚举.值读取(中的最终JsonReader)
抛出IOException{
in.beginObject();
@可空
字符串类型=null;
@可空
字符串名称=null;
while(在.hasNext()中){
开关(在.nextName()中){
案例“类型”:
type=in.nextString();
打破
案例“名称”:
name=in.nextString();
打破
违约:
in.skipValue();
{"type":"Single","name":"Only"}