Spark中使用Encoders.bean时集合属性被编码为空JSON的问题
问题:Spark Dataset中Set类型字段显示为空{}的原因及解决办法
定义的Java模型类(使用Lombok)
@Getter @Setter @Builder // lombok public class MyClass { int id; String name; Set<String> hobbies; }
创建Dataset的代码
MyClass myClassObj1 = MyClass.builder() .id(1) .name("Jack") .hobbies(ImmutableSet.of("mountaineering", "cycling")) .build(); MyClass myClassObj2 = MyClass.builder() .id(2) .name("Jill") .hobbies(ImmutableSet.of("rhymes", "poetry")) .build(); Dataset<MyClass> myClassDataset = sparkSession.createDataset( ImmutableList.of(myClassObj1, myClassObj2), Encoders.bean(MyClass.class));
执行现象
执行myClassDataset.show()后,hobbies列显示为空:
+----------+-------+---------+ |id |hobbies| name | +----------+-------+---------+ | 1| {}|Jack | | 2| {}|Jill | +----------+-------+---------+
查看schema输出:
Seq(StructField(id,IntegerType,true), StructField(hobbies,Seq(),true), StructField(name,StringType,true))
原因分析
- Spark的
Encoders.bean()编码器仅默认支持java.util.List类型(对应Spark的Seq类型),无法正确解析java.util.Set类型。从schema输出能看到,hobbies被识别为Seq()类型,但实际存储的是Set实例,类型不匹配导致序列化/反序列化时数据丢失,最终显示为空{}。 - Lombok生成的
Set类型字段getter方法返回Set实例,不符合Spark Bean编码器对List类型的读取期望,进而无法正确提取数据。
解决办法
方法1:将Set改为List
直接修改模型类的hobbies字段类型为List<String>,同时调整对象构建时的集合类型:
@Getter @Setter @Builder public class MyClass { int id; String name; List<String> hobbies; }
构建对象时使用ImmutableList:
.hobbies(ImmutableList.of("mountaineering", "cycling"))
方法2:使用Kryo编码器替代Bean编码器
如果必须保留Set类型,可以使用Encoders.kryo(),它对复杂类型的序列化支持更好:
Dataset<MyClass> myClassDataset = sparkSession.createDataset( ImmutableList.of(myClassObj1, myClassObj2), Encoders.kryo(MyClass.class));
注意:使用Kryo后,字段schema会变为BinaryType,无法直接对hobbies字段进行SQL类操作。
方法3:添加兼容List类型的getter方法
在MyClass中新增一个返回List类型的getter方法,供Spark编码器识别,同时保留原Set字段的getter:
@Getter @Setter @Builder public class MyClass { int id; String name; Set<String> hobbies; // 供Spark编码器使用的兼容getter public List<String> getHobbiesList() { return new ArrayList<>(hobbies); } }
后续可通过withColumn将hobbiesList重命名为hobbies,保证字段名一致。
内容的提问来源于stack exchange,提问作者Yo Yo Money Singh
相关产品推荐
相关产品推荐

