Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Reflection to Configure Jackson 2.12 Features #19918

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.BeanUtil;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
Expand All @@ -34,6 +37,46 @@
* Jackson based implementation of the {@link JsonSerializer} and {@link MemberNameConverter} interfaces.
*/
public final class JacksonJsonSerializer implements JsonSerializer, MemberNameConverter {
private static final String ACCESSOR_NAMING_STRATEGY =
"com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy";
private static final String ACCESSOR_NAMING_STRATEGY_PROVIDER = ACCESSOR_NAMING_STRATEGY + ".Provider";
private static final MethodHandle GET_ACCESSOR_NAMING;
private static final MethodHandle FOR_POJO;
private static final MethodHandle FIND_NAME_FOR_IS_GETTER;
private static final MethodHandle FIND_NAME_FOR_REGULAR_GETTER;
private static final boolean USE_REFLECTION_FOR_MEMBER_NAME;

static {
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

MethodHandle getAccessorNaming = null;
MethodHandle forPojo = null;
MethodHandle findNameForIsGetter = null;
MethodHandle findNameForRegularGetter = null;
boolean useReflectionForMemberName = false;

try {
Class<?> accessorNamingStrategyProviderClass = Class.forName(ACCESSOR_NAMING_STRATEGY_PROVIDER);
Class<?> accessorNamingStrategyClass = Class.forName(ACCESSOR_NAMING_STRATEGY);
getAccessorNaming = publicLookup.findVirtual(MapperConfig.class, "getAccessorNaming",
MethodType.methodType(accessorNamingStrategyProviderClass));
forPojo = publicLookup.findVirtual(accessorNamingStrategyProviderClass, "forPOJO",
MethodType.methodType(accessorNamingStrategyClass, MapperConfig.class, AnnotatedClass.class));
findNameForIsGetter = publicLookup.findVirtual(accessorNamingStrategyClass, "findNameForIsGetter",
MethodType.methodType(String.class, AnnotatedMethod.class, String.class));
findNameForRegularGetter = publicLookup.findVirtual(accessorNamingStrategyClass, "findNameForRegularGetter",
MethodType.methodType(String.class, AnnotatedMethod.class, String.class));
useReflectionForMemberName = true;
} catch (Throwable ignored) {
}

GET_ACCESSOR_NAMING = getAccessorNaming;
FOR_POJO = forPojo;
FIND_NAME_FOR_IS_GETTER = findNameForIsGetter;
FIND_NAME_FOR_REGULAR_GETTER = findNameForRegularGetter;
USE_REFLECTION_FOR_MEMBER_NAME = useReflectionForMemberName;
alzimmermsft marked this conversation as resolved.
Show resolved Hide resolved
}

private final ClientLogger logger = new ClientLogger(JacksonJsonSerializer.class);

private final ObjectMapper mapper;
Expand Down Expand Up @@ -195,14 +238,42 @@ private String removePrefix(Method method) {

AnnotatedClass annotatedClass = AnnotatedClassResolver.resolve(config,
mapper.constructType(method.getDeclaringClass()), null);
AccessorNamingStrategy accessorNamingStrategy = config.getAccessorNaming().forPOJO(config, annotatedClass);

AnnotatedMethod annotatedMethod = new AnnotatedMethod(null, method, null, null);
String name = accessorNamingStrategy.findNameForIsGetter(annotatedMethod, annotatedMethod.getName());
String annotatedMethodName = annotatedMethod.getName();

String name = null;
if (USE_REFLECTION_FOR_MEMBER_NAME) {
name = removePrefixWithReflection(config, annotatedClass, annotatedMethod, annotatedMethodName, logger);
}

if (name == null) {
name = accessorNamingStrategy.findNameForRegularGetter(annotatedMethod, annotatedMethod.getName());
name = removePrefixWithBeanUtils(annotatedMethod);
}

return name;
}

private static String removePrefixWithReflection(MapperConfig<?> config, AnnotatedClass annotatedClass,
AnnotatedMethod method, String methodName, ClientLogger logger) {
try {
Object accessorNamingStrategy = FOR_POJO.invoke(GET_ACCESSOR_NAMING.invoke(config), config, annotatedClass);
alzimmermsft marked this conversation as resolved.
Show resolved Hide resolved


String name = (String) FIND_NAME_FOR_IS_GETTER.invoke(accessorNamingStrategy, method, methodName);
if (name == null) {
name = (String) FIND_NAME_FOR_REGULAR_GETTER.invoke(accessorNamingStrategy, method, methodName);
}

return name;
} catch (Throwable ex) {
logger.verbose("Failed to find member name with AccessorNamingStrategy, returning null.", ex);
return null;
alzimmermsft marked this conversation as resolved.
Show resolved Hide resolved
}
}

@SuppressWarnings("deprecation")
private static String removePrefixWithBeanUtils(AnnotatedMethod annotatedMethod) {
return BeanUtil.okNameForGetter(annotatedMethod, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.cfg.MapperBuilder;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
Expand All @@ -34,6 +32,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -59,6 +60,49 @@
public class JacksonAdapter implements SerializerAdapter {
private static final Pattern PATTERN = Pattern.compile("^\"*|\"*$");

private static final String MUTABLE_COERCION_CONFIG = "com.fasterxml.jackson.databind.cfg.MutableCoercionConfig";
private static final String COERCION_INPUT_SHAPE = "com.fasterxml.jackson.databind.cfg.CoercionInputShape";
private static final String COERCION_ACTION = "com.fasterxml.jackson.databind.cfg.CoercionAction";

private static final MethodHandle COERCION_CONFIG_DEFAULTS;
private static final MethodHandle SET_COERCION;
private static final Object COERCION_INPUT_SHAPE_EMPTY_STRING;
private static final Object COERCION_ACTION_AS_NULL;
private static final boolean USE_REFLECTION_TO_SET_COERCION;

static {
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

MethodHandle coercionConfigDefaults = null;
MethodHandle setCoercion = null;
Object coercionInputShapeEmptyString = null;
Object coercionActionAsNull = null;
boolean useReflectionToSetCoercion = false;

try {
Class<?> mutableCoercionConfig = Class.forName(MUTABLE_COERCION_CONFIG);
Class<?> coercionInputShapeClass = Class.forName(COERCION_INPUT_SHAPE);
Class<?> coercionActionClass = Class.forName(COERCION_ACTION);

coercionConfigDefaults = publicLookup.findVirtual(ObjectMapper.class, "coercionConfigDefaults",
MethodType.methodType(mutableCoercionConfig));
setCoercion = publicLookup.findVirtual(mutableCoercionConfig, "setCoercion",
MethodType.methodType(mutableCoercionConfig, coercionInputShapeClass, coercionActionClass));
coercionInputShapeEmptyString = publicLookup.findStaticGetter(coercionInputShapeClass, "EmptyString",
coercionInputShapeClass).invoke();
coercionActionAsNull = publicLookup.findStaticGetter(coercionActionClass, "AsNull", coercionActionClass)
.invoke();
useReflectionToSetCoercion = true;
} catch (Throwable ignored) {
}

COERCION_CONFIG_DEFAULTS = coercionConfigDefaults;
SET_COERCION = setCoercion;
COERCION_INPUT_SHAPE_EMPTY_STRING = coercionInputShapeEmptyString;
COERCION_ACTION_AS_NULL = coercionActionAsNull;
USE_REFLECTION_TO_SET_COERCION = useReflectionToSetCoercion;
alzimmermsft marked this conversation as resolved.
Show resolved Hide resolved
}

private final ClientLogger logger = new ClientLogger(JacksonAdapter.class);

/**
Expand Down Expand Up @@ -103,8 +147,17 @@ public JacksonAdapter() {
.enable(FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL)
.build();

this.xmlMapper.coercionConfigDefaults()
.setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);

if (USE_REFLECTION_TO_SET_COERCION) {
try {
Object object = COERCION_CONFIG_DEFAULTS.invoke(this.xmlMapper);
SET_COERCION.invoke(object, COERCION_INPUT_SHAPE_EMPTY_STRING, COERCION_ACTION_AS_NULL);
} catch (Throwable e) {
logger.verbose("Failed to set coercion actions.", e);
}
} else {
logger.verbose("Didn't set coercion defaults as it wasn't found on the classpath.");
}

ObjectMapper flatteningMapper = initializeMapperBuilder(JsonMapper.builder())
.addModule(FlatteningSerializer.getModule(simpleMapper()))
Expand Down