All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kiwiproject.json.LoggingDeserializationProblemHandler Maven / Gradle / Ivy

package org.kiwiproject.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;

/**
 * A Jackson {@link DeserializationProblemHandler} that logs and keeps track of unknown properties during JSON
 * deserialization. Any unexpected/unknown properties are logged as warnings and stored in-memory in a
 * {@link ConcurrentMap}.
 * 

* Optionally, you can supply a {@link BiConsumer} to the constructor if you want to be notified when unknown * properties are encountered. This consumer should be thread-safe! *

* Note that jackson-core and jackson-databind must be available at runtime. * * @implNote Currently the in-memory map will continue to grow unbounded. In the expected scenario, unknown properties * will be relatively rare so we don't expect huge numbers of them. In addition, only unique unknown properties are * stored. The {@link #clearUnexpectedPaths()} method is provided to allow clearing all unexpected paths, for example * read the unexpected paths for analytics and then clear them. */ @Slf4j public class LoggingDeserializationProblemHandler extends DeserializationProblemHandler { private final ConcurrentMap unexpectedPaths = new ConcurrentHashMap<>(); private final BiConsumer> unknownPropertyConsumer; /** * Create a new instance with a no-op {@code unknownPropertyConsumer}. */ public LoggingDeserializationProblemHandler() { this((propertyName, aClass) -> { /* no-op */}); } /** * Create a new instance with the given {@code unknownPropertyConsumer}. * * @param unknownPropertyConsumer the consumer to be notified whenever unknown properties are encountered */ public LoggingDeserializationProblemHandler(BiConsumer> unknownPropertyConsumer) { this.unknownPropertyConsumer = unknownPropertyConsumer; } @Override public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer deserializer, Object beanOrClass, String propertyName) throws IOException { var path = p.getParsingContext().pathAsPointer().toString().replace("/", "."); var className = beanOrClass.getClass().getName(); LOG.warn("Unable to deserialize path: '{}' for class: {}", path, className); p.skipChildren(); var key = className + path; var val = className + " -> " + path; unexpectedPaths.putIfAbsent(key, val); notifyConsumer(beanOrClass, path); return true; // to indicate the problem is "resolved" } private void notifyConsumer(Object beanOrClass, String path) { try { unknownPropertyConsumer.accept(path, beanOrClass.getClass()); } catch (Exception e) { LOG.error("unknownPropertyConsumer threw exception", e); } } /** * Clear all unexpected path information. */ public void clearUnexpectedPaths() { unexpectedPaths.clear(); } /** * Return the unique unexpected paths in the format {@code className -> propertyName}. * * @return unexpected paths */ public Set getUnexpectedPaths() { return Set.copyOf(unexpectedPaths.values()); } /** * Return the unique unexpected paths in the format {@code className.propertyName}. * * @return unexpected paths */ public Set getUnexpectedPropertyPaths() { return Set.copyOf(unexpectedPaths.keySet()); } /** * Current total count of unique unknown properties. * * @return total count of unique unknown property paths */ public long getUnknownPropertyCount() { return unexpectedPaths.size(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy