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?
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.