使用 Jackson 库在 Java 中反序列化 Map<Enum<?>、Object>

问题描述 投票:0回答:2

我需要使用 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"
}

在序列化期间,然后能够反序列化它。

java json enums jackson jackson-databind
2个回答
0
投票

我解决了编写自定义反序列化器的问题,该反序列化器将 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;

0
投票

枚举和映射在这里似乎不是正确的工具(问题太多),如果可能的话,我会尝试重新设计。不过,这可以通过包装类和键的自定义序列化器/反序列化器来实现 - 在

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<>() {
});
© www.soinside.com 2019 - 2024. All rights reserved.