Don't think there is easy and clean solution for this problem. But here is some thoughts how you can solve it (Gist demo for both cases):
Option 1: Add @JsonIgnore
above property and @JsonAnyGetter
in the top level bean. Easy to implement but not nice to have static ObjectMapper in bean and will have to copy this code to every been with Parameter property
public class A {
@JsonIgnore
Parameter parameter;
// can put ObjectMapper and most of this code in util class
// and just use JacksonUtils.toMap(parameter) as return of JsonAnyGetter
private static ObjectMapper mapper = new ObjectMapper();
/************************ Serialization ************************/
@JsonAnyGetter
private Map<String, Object> parameterAsMap(){
return mapper.convertValue(parameter, Map.class);
}
/************************ Deserialization **********************/
@JsonAnySetter
private void parameterFromMap(String key, JsonNode value) {
try {
parameter = mapper.readValue(String.format("{\"%s\":%s}", key,value),
Parameter.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Option 2: @JsonIgnore
property and register custom serializer/deserializer for root A
class
SimpleModule module = new SimpleModule();
module.addSerializer(A.class, new ASerializer());
module.addDeserializer(A.class, new ADeserializer());
mapper.registerModule(module);
Can't use @JsonSerialize
above A class, because serializers and deserializers inner ObjectMapper will also use this annotation, but you need to setup it to use default serializer/deserializer and not itself recursively. Or you could implement something like https://mcmap.net/q/156331/-how-do-i-call-the-default-deserializer-from-a-custom-deserializer-in-jackson if your really want the annotation
And serializer + deserializer would look something like this (Unoptimized and just a prove of concept):
/************************ Serialization ************************/
public static class ASerializer extends JsonSerializer<A> {
private static ObjectMapper m = new ObjectMapper();
@Override
public void serialize(A value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Map defaults = m.convertValue(value, Map.class);
Map params = m.convertValue(value.getParameter(), Map.class);
defaults.putAll(params);
gen.writeObject(defaults);
}
}
/************************ Deserialization **********************/
public static class ADeserializer extends JsonDeserializer<A> {
private static ObjectMapper m = new ObjectMapper();
private static String[] subtipes = {"integerParam", "comboParam"};
public ADeserializer() {
m.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
@Override
public A deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
TreeNode node = m.readTree(p);
A a = m.convertValue(node, A.class);
// hardcoded , probably can be done dynamically
// with annotations inspection
for (String key : subtipes) {
TreeNode value = node.get(key);
if (value != null) {
String json = String.format("{\"%s\":%s}", key, value);
a.setParameter(m.readValue(json, Parameter.class));
break;
}
}
return a;
}
}
Generic deserializer would be pretty hard to write. But this question is about serialization anyway according to the question body.
{ integerParam:{ regEx: regExVal } }
– Mcgannoninclude = As.WRAPPER_OBJECT
from Parmeter class and try. – Gaily