Мне нужно добавить перехватчик в Джексоне при десериализации JSON, который проверяет созданный POJO и имеет доступ к объекту Json для целей мониторинга / регистрации.
Для этого я добавил простой десериализатор, который явно десериализуется в POJO следующим образом:
@Override
public T deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
ABC transaction = codec.treeToValue(node, klass);
if (ObjectUtil.hasAllFieldsNull(transaction)) {
MonitorClient monitor = (MonitorClient) ctx.findInjectableValue("monitor", null, null);
monitor.fireError..etc
}
return transaction;
}
Я комментировал класс ABC следующим образом
@JsonDeserialize(as = ABC.class, using = ABCDeserializer.class)
class ABC {}
Проблема в том, что это вызывает StackOverflowError
java.lang.StackOverflowError: null
at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:246) ~[?:?]
at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:68) ~[?:?]
at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:15) ~[?:?]
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3972) ~[?:?]
at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:2372) ~[?:?]
Один из способов решения этой проблемы, который я нашел, заключается в использовании шаблона для полиморфных jsons и создании интерфейса IABC следующим образом
@JsonDeserialize(using = ABCDeserializer.class)
interface IABC {}
И изменить определение ABC и аннотации на
@JsonDeserialize(as = ABC.class)
class ABC implements IABC {}
Но мне не нравится этот подход, поскольку мне приходится выполнять эту десериализацию для каждого десериализуемого нами объекта, и это приводит к множеству «бесполезных» интерфейсов.
Можно ли решить эту проблему проще?
Edit:
То, что я закончил, это * 1024
Добавьте BeanDeserializerModifier, который создает пользовательский десериализатор, который делегирует сериализатору по умолчанию и перехватывает конструкцию json post:
public class MonitoringObjectDeserializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Abcd.class) {
return new MonitoringBeanDeserializer((BeanDeserializerBase) deserializer);
}
return deserializer;
}
}
Десериализатор выглядит примерно так:
public class MonitoringBeanDeserializer<T> extends BeanDeserializer {
private static final long serialVersionUID = 1L;
private final JsonDeserializer<?> deserializer;
public MonitoringBeanDeserializer(BeanDeserializerBase base) {
super(base);
this.deserializer = base;
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
JsonLocation initialLocation = p.getCurrentLocation();
T transaction = ObjectUtil.uncheckedCast(deserializer.deserialize(p, ctx));
if (ObjectUtil.hasAllFieldsNull(transaction)) {
String txJson = extractJson(initialLocation, p.getCurrentLocation());
MonitorClient monitor = (MonitorClient) ctx.findInjectableValue("monitor", null, null);
monitor.abcd..
}
return transaction;
}
private static String extractJson(JsonLocation initialLocation, JsonLocation finalLocation) {
Object source = initialLocation.getSourceRef();
String sourceJson;
if (source instanceof StringReader) {
sourceJson = (String) ObjectUtil.get(source, "str");
} else {
sourceJson = (String) source;
}
int start = (int) initialLocation.getCharOffset() - 1;
int end = (int) finalLocation.getCharOffset();
return sourceJson.substring(start, end);
}
}
Больше не нужно аннотаций и прочего. Это отличный хак, но я бы предпочел сделать это, чем создавать много интерфейсов.