我需要使用 Jackson 库以 JSON 格式序列化和反序列化 Java 地图。 映射类型是
Map<Enum<?>, Object>
,其目的是存储由键值对构成的配置,其中键是来自第三方库的枚举实例(因此我无法控制它们的定义)。
键可以是不同类型的枚举,而值可以是 int
、double
或 String
。
另外,您可以假设不同的枚举具有不同的实例名称。
序列化工作正常:
public class Main {
public enum IntParam {I1, I2, I3}
public enum DoubleParam {D1, D2, D3}
public enum StringParam {S1, S2, S3}
public static void main(final String[] args) {
Map<Enum<?>, Object> config = Map.of(
IntParam.I1, 0,
DoubleParam.D2, 1.0,
StringParam.S3, "sss"
);
new ObjectMapper().writeValueAsString(config)
// {"I1": 1, "D2": 1.0, "S3": "sss"}
}
问题在于反序列化上述对象:
String json = "{\"I1\": 1, \"D2\": 1.0, \"S3\": \"sss\"}";
new ObjectMapper().readValue(json, new TypeReference<Map<Enum<?>, Object>>())
失败,因为 Jackson 无法知道哪个枚举包含名为
I1
、D2
和 S3
的实例。
我想实现类似的目标
{
"com.external.company.IntParam.I1": 1,
"com.external.company.DoubleParam.D2": 1.0,
"com.external.company.StringParam.S3": "sss"
}
在序列化期间,然后能够反序列化它。
我解决了编写自定义反序列化器的问题,该反序列化器将 JSON 对象加载到
HashMap<String, Object>
中,然后循环遍历不同的枚举类型,查找与映射中的键同名的枚举实例(这依赖于以下事实:所有枚举实例在所有涉及的枚举类型中具有不同的名称)。
public class ConfigMapDeserializer extends JsonDeserializer<Map<Enum<?>, Object>> {
@Override
public Map<Enum<?>, Object> deserialize(
final JsonParser jp, final DeserializationContext ctxt
) throws IOException, JsonProcessingException {
final HashMap<String, Object> rawObject = jp.readValueAs(new TypeReference<HashMap<String, Object>>() {});
final HashMap<Enum<?>, Object> config = new HashMap<>(rawObject.size());
for (final Entry<String, Object> entry: rawObject.entrySet()) {
final String name = entry.getKey();
Enum<?> param = null;
try { param = IntParam.valueOf(name); } catch (final Exception e) { /* no-op */ }
try { param = DoubleParam.valueOf(name); } catch (final Exception e) { /* no-op */ }
try { param = StringParam.valueOf(name); } catch (final Exception e) { /* no-op */ }
if (param == null) {
// DO SOMETHING...
} else {
config.put(param, entry.getValue());
}
}
return config;
}
}
最后,我用
装饰了配置属性@JsonDeserialize(using=ConfigMapDeserializer.class) Map<Enum<?>, Object> config;
枚举和映射在这里似乎不是正确的工具(问题太多),如果可能的话,我会尝试重新设计。不过,这可以通过包装类和键的自定义序列化器/反序列化器来实现 - 在
Enum
上应用 mixin 来实现多态序列化似乎是不可能的(不过,不要相信我的话,我可能是错的)。
枚举的包装:
@JsonSerialize(keyUsing = EnumWrapperKeySerializer.class)
@JsonDeserialize(keyUsing = EnumWrapperKeyDeserializer.class)
public record EnumWrapper(Enum<?> anEnum) {
}
注意
keyUsing
,这是为了制作专门用于映射键的反序列化器。
一个反序列化器,用于保存有关枚举类和实例的信息:
class EnumWrapperKeySerializer extends JsonSerializer<EnumWrapper> {
@Override
public void serialize(EnumWrapper value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeFieldName(value.anEnum().getClass().getName() + "." + value.anEnum().name());
}
}
一个反序列化器,用于解析以纠正枚举和该枚举的实例:
public class EnumWrapperKeyDeserializer extends KeyDeserializer {
@Override
public Object deserializeKey(String key, DeserializationContext ctxt) {
int index = key.lastIndexOf(".");
String enumName = key.substring(0, index);
Class<? extends Enum> enumClass;
try {
enumClass = (Class<? extends Enum>) Class.forName(enumName);
} catch (ClassNotFoundException e) {
//probably should not happen
throw new RuntimeException(e);
}
String enumValue = key.substring(index + 1);
return new EnumWrapper(Enum.valueOf(enumClass, enumValue));
}
}
示例:
Map<EnumWrapper, Object> map = Map.of(
new EnumWrapper(IntParam.I1), 1,
new EnumWrapper(DoubleParam.D2), 1.0,
new EnumWrapper(StringParam.S3), "sss");
ObjectMapper mapper = new ObjectMapper();
String res = mapper.writeValueAsString(map);
System.out.println(res);
Map<EnumWrapper, Object> map2 = mapper.readValue(res, new TypeReference<>() {
});