英文:
Java Static final instances hierarchy deserialization
问题
我有3个类:
public class Base {
private int code;
private static final Map<String, Map<Integer, Base>> ALL_MAPS =
new HashMap<>();
protected Base(int code) {
this.code = code;
getConstMap(this.getClass()).put(code, this);
}
protected Map<Integer, Base> getConstMap(Class<?> clazz) {
Map<Integer, Base> res;
if ((res = ALL_MAPS.get(clazz.getName())) == null) {
res = new HashMap<>();
ALL_MAPS.put(clazz.getName(), res);
}
return res;
}
//...
}
public class AConstants extends Base {
public static final AConstants A_1 = new AConstants(1,
"sample1");
public static final AConstants A_2 = new AConstants(2, "sample2");
//...
private final String text;
private AConstants(int code, String text) {
super(code);
this.text = text;
}
}
public class BConstants extends Base {
public static final BConstants B_1 = new BConstants(1,
"sample1", "moreText");
public static final BConstants B_2 = new BConstants(2,
"sample2", "moreText");
//...
private final String text;
private final String moreText;
private BConstants(int code, String text, String moreText) {
super(code);
this.text = text;
this.moreText = moreText;
}
}
我想要实现的是在Base
类中有一个反序列化(和序列化)静态final实例的基础方法。显然,由于在编译期间无法从静态上下文中引用泛型类型(另外我显然无法获取CONST
的类),我无法这样做:
@JsonCreator
public static CONST deserialize(@JsonProperty("code") int code) {
// 应该获取CONST的类并根据code检索静态final实例
return ALL_MAPS.get(CONST.class).get(code);
}
@JsonValue
public Integer serialize() {
// 返回所需类的静态final实例的代码
return getKeyByValue(ALL_MAPS.get(CONST.class), this);
}
是否有办法使用Jackson实现这一点?如果不行,我该如何编写一个自定义的序列化程序/反序列化程序?
英文:
I have 3 classes:
public class Base {
private int code;
private static final Map<String, Map<Integer, Base>> ALL_MAPS =
new HashMap<>();
protected Base(int code) {
this.code = code;
getConstMap(this.getClass()).put(code, this);
}
protected Map<Integer, Base> getConstMap(Class<?> clazz) {
Map<Integer, Base> res;
if ((res = ALL_MAPS.get(clazz.getName())) == null) {
res = new HashMap<>();
ALL_MAPS.put(clazz.getName(), res);
}
return res;
}
//...
}
public class AConstants extends Base {
public static final AConstants A_1 = new AConstants(1,
"sample1");
public static final AConstants A_2 = new AConstants(2, "sample2");
//...
private final String text;
private AConstants(int code, String text) {
super(code);
this.text = text;
}
}
public class BConstants extends Base {
public static final BConstants B_1 = new BConstants(1,
"sample1", "moreText");
public static final BConstants B_2 = new BConstants(2,
"sample2", "moreText");
//...
private final String text;
private final String moreText;
private BConstants(int code, String text, String moreText) {
super(code);
this.text = text;
this.moreText = moreText;
}
}
What I want to achieve is having a base method for deserializing (and serealizing) static final instances in the Base
class. Obviously, I can't do smth like this due to generic type being unable to be referenced from static context during compilation (plus I obviously can't get the class of CONST
):
@JsonCreator
public static CONST deserialize(@JsonProperty("code") int code) {
// Should get the class of CONST and retrieve static final instance by code
return ALL_MAPS.get(CONST.class).get(code);
}
@JsonValue
public Integer serialize() {
// Return code for static final instance of the necessary class
return getKeyByValue(ALL_MAPS.get(CONST.class), this);
}
Is there a way to achieve this with Jackson? Or if not, how do I write a custom serializer/deserializer for this?
答案1
得分: 0
如果有人有类似的用例,以下是我是如何做的。 Base
类基本上是所有 static final
实例(或者我应该称它们为 constants
)的占位符 - ALL_MAPS
包含键,作为扩展 Base
的类的名称,以及值 - 一个 Map
,键为实例的代码,值为实例本身。当 Base
的子类加载到 JVM 中时,其中的所有 constants
(因为它们是 static
)都会放入 ALL_MAP
。因此,在反序列化时,除了将类加载到内存中之外,我只需要一个 constant
的类和一个指向它的键(code
)。我可以在自定义反序列化器中像这样获取类:https://github.com/FasterXML/jackson-databind/issues/2711
之后,剩下的就是将类加载到内存中 - 我通过反射来实现,通过在具有 null
值的任何静态字段上调用 get
方法来实现,因为我根本不想允许创建 const
子类的实例。然后 - 读取包含 code
的 JSON 节点并获取 constant
。
作为一种说明:在 Base
类上,我使用了 @JsonDeserialize(using = BaseConstDeserializer.class)
,其中 BaseConstDeserializer
是自定义的反序列化器。
这是反序列化器的代码:
public class BaseConstDeserializer<CONST extends BaseConst> extends StdDeserializer<CONST> implements ContextualDeserializer {
private final Class<CONST> deserializedFieldClazz;
protected BaseConstDeserializer(Class<CONST> deserializedFieldClazz) {
super(BaseConst.class);
this.deserializedFieldClazz = deserializedFieldClazz;
}
@Override
@SuppressWarnings("unchecked")
public CONST deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = jp.getCodec().readTree(jp);
Integer code = JsonUtils.readSingleNode(node, "code", JsonNode::asInt, ValidUtils::isInteger);
if (code != null) {
BaseConst.initConst(deserializedFieldClazz);
return (CONST) BaseConst.getConstAndClearMap(code, deserializedFieldClazz);
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty property) throws JsonMappingException {
Class<CONST> rawClass = (Class<CONST>) property.getType().getRawClass();
return new BaseConstDeserializer<>(rawClass);
}
}
我唯一能猜到的缺点是,我需要自己管理内存使用情况 - 每次反序列化 JSON 时清除映射。我知道在 Spring 中可以使用 @Scope
来将 bean 实例的 "生命周期" 设置为请求的范围,但在这种情况下,我根本不喜欢创建和拥有这些实例。这可能是一种不正统的方法,但它适合我的用例,我喜欢这个解决方案。
无论如何,如果有人看到其他潜在的优缺点,我愿意听取意见。
英文:
In case someone has a similiar use case, here's how I did it. Base
class is basically a placeholder for all the static final
instances (or should I call them constants
) - ALL_MAPS
contains key as the name of the class which extends Base
and as of value - a Map
of keys - code of the instance and the instance itself as value. When the subclass of Base
is loaded into the JVM, all the constants
in it (as they are static
) are placed in the ALL_MAP
. So when deserializing, apart from loading the class into the memory, I only need a class of the constant
and a key to it (code
). I can get the class in the custom deserializer just like this: https://github.com/FasterXML/jackson-databind/issues/2711
After that, all is left is to load the class into memory - I achieved it via reflection by calling get
method on any static field with null
value as I don't want to allow creating instances of const
subclasses at all. And then - to read the json node which contains the code
and get the constant
.
As a note: on the Base
class I used @JsonDeserialize(using = BaseConstDeserializer.class)
where BaseConstDeserializer
is the custom deserializer.
Here is the code of the deserializer:
public class BaseConstDeserializer<CONST extends BaseConst> extends StdDeserializer<CONST> implements ContextualDeserializer {
private final Class<CONST> deserializedFieldClazz;
protected BaseConstDeserializer(Class<CONST> deserializedFieldClazz) {
super(BaseConst.class);
this.deserializedFieldClazz = deserializedFieldClazz;
}
@Override
@SuppressWarnings("unchecked")
public CONST deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = jp.getCodec().readTree(jp);
Integer code = JsonUtils.readSingleNode(node, "code", JsonNode::asInt, ValidUtils::isInteger);
if (code != null) {
BaseConst.initConst(deserializedFieldClazz);
return (CONST) BaseConst.getConstAndClearMap(code, deserializedFieldClazz);
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty property) throws JsonMappingException {
Class<CONST> rawClass = (Class<CONST>) property.getType().getRawClass();
return new BaseConstDeserializer<>(rawClass);
}
}
The only drawback I can guess here is that I need to manage the memory usage by myself - by clearing the map every time I deserialized json. I know that in Spring I can use @Scope
to set the "lifespan" of an instance of bean to the scope of request but I don't like creating and having those instances in this case at all. This is propably an unorthodox approach, but it serves my case and I like the solution.
Anyway, if someone sees any other potentials cons and pros of it, I'd like to hear.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论