diff --git a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml
index ecf8041030041..060961b962ab7 100755
--- a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml
+++ b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml
@@ -626,4 +626,8 @@ the main ServiceBusClientBuilder. -->
+
+
+
diff --git a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml
index 3726620b1b49c..2f5b5c2e426b3 100755
--- a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml
+++ b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml
@@ -153,6 +153,13 @@
+
+
+
+
+
+
+
diff --git a/sdk/agrifood/azure-verticals-agrifood-farming/pom.xml b/sdk/agrifood/azure-verticals-agrifood-farming/pom.xml
index 63962ec632382..58c7633a4737b 100644
--- a/sdk/agrifood/azure-verticals-agrifood-farming/pom.xml
+++ b/sdk/agrifood/azure-verticals-agrifood-farming/pom.xml
@@ -32,6 +32,9 @@
true
+
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
+
diff --git a/sdk/confidentialledger/azure-security-confidentialledger/pom.xml b/sdk/confidentialledger/azure-security-confidentialledger/pom.xml
index 5402b6dd84345..fe4186a0e02aa 100644
--- a/sdk/confidentialledger/azure-security-confidentialledger/pom.xml
+++ b/sdk/confidentialledger/azure-security-confidentialledger/pom.xml
@@ -32,6 +32,9 @@
true
+
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
+
diff --git a/sdk/core/azure-core-management/src/main/java/com/azure/core/management/implementation/serializer/AzureJacksonAdapter.java b/sdk/core/azure-core-management/src/main/java/com/azure/core/management/implementation/serializer/AzureJacksonAdapter.java
index a2a6203ec72e9..306f77c55cf48 100644
--- a/sdk/core/azure-core-management/src/main/java/com/azure/core/management/implementation/serializer/AzureJacksonAdapter.java
+++ b/sdk/core/azure-core-management/src/main/java/com/azure/core/management/implementation/serializer/AzureJacksonAdapter.java
@@ -14,7 +14,6 @@ public final class AzureJacksonAdapter extends JacksonAdapter {
* Creates an instance of the Azure flavored Jackson adapter.
*/
public AzureJacksonAdapter() {
- super();
- serializer().registerModule(ManagementErrorDeserializer.getModule(simpleMapper()));
+ super((outerMapper, innerMapper) -> outerMapper.registerModule(ManagementErrorDeserializer.getModule(innerMapper)));
}
}
diff --git a/sdk/core/azure-core-management/src/test/java/com/azure/core/management/exception/ManagementExceptionTests.java b/sdk/core/azure-core-management/src/test/java/com/azure/core/management/exception/ManagementExceptionTests.java
index 10361b2f9d7e1..ed76206353601 100644
--- a/sdk/core/azure-core-management/src/test/java/com/azure/core/management/exception/ManagementExceptionTests.java
+++ b/sdk/core/azure-core-management/src/test/java/com/azure/core/management/exception/ManagementExceptionTests.java
@@ -43,6 +43,18 @@ public void testSubclassDeserialization() throws IOException {
Assertions.assertEquals("ResourceGroupNotFound", managementError.getCode());
}
+ @Test
+ public void testCaseInsensitiveSubclassDeserialization() throws IOException {
+ final String errorBody = "{\"error\":{\"Code\":\"WepAppError\",\"MESSAGE\":\"Web app error.\",\"Details\":[{\"code\":\"e\"}],\"TaRgeT\":\"foo\"}}";
+
+ SerializerAdapter serializerAdapter = SerializerFactory.createDefaultManagementSerializerAdapter();
+ WebError webError = serializerAdapter.deserialize(errorBody, WebError.class, SerializerEncoding.JSON);
+ Assertions.assertEquals("WepAppError", webError.getCode());
+ Assertions.assertEquals("Web app error.", webError.getMessage());
+ Assertions.assertEquals(1, webError.getDetails().size());
+ Assertions.assertEquals("foo", webError.getTarget());
+ }
+
@Test
public void testDeserializationInResource() throws IOException {
final String virtualMachineJson = "{\"properties\":{\"instanceView\":{\"patchStatus\":{\"availablePatchSummary\":{\"error\":{}}}}}}";
diff --git a/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializer.java b/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializer.java
index eafd084a72cc2..6cf381c0524cc 100644
--- a/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializer.java
+++ b/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializer.java
@@ -3,96 +3,35 @@
package com.azure.core.serializer.json.jackson;
-import com.azure.core.util.CoreUtils;
+import com.azure.core.implementation.jackson.ObjectMapperShim;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.serializer.JsonSerializer;
import com.azure.core.util.serializer.MemberNameConverter;
import com.azure.core.util.serializer.TypeReference;
-import com.fasterxml.jackson.annotation.JsonGetter;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-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.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;
-import java.lang.reflect.Modifier;
/**
* 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 ex) {
- new ClientLogger(JacksonJsonSerializer.class)
- .verbose("Failed to retrieve MethodHandles used to get naming strategy. Falling back to BeanUtils.",
- ex);
- }
-
- 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;
- }
private final ClientLogger logger = new ClientLogger(JacksonJsonSerializer.class);
- private final ObjectMapper mapper;
- private final TypeFactory typeFactory;
+ private final ObjectMapperShim mapper;
/**
* Constructs a {@link JsonSerializer} using the passed Jackson serializer.
*
* @param mapper Configured Jackson serializer.
*/
- JacksonJsonSerializer(ObjectMapper mapper) {
+ JacksonJsonSerializer(ObjectMapperShim mapper) {
this.mapper = mapper;
- this.typeFactory = mapper.getTypeFactory();
}
@Override
@@ -102,7 +41,7 @@ public T deserializeFromBytes(byte[] data, TypeReference typeReference) {
}
try {
- return mapper.readValue(data, typeFactory.constructType(typeReference.getJavaType()));
+ return mapper.readValue(data, typeReference.getJavaType());
} catch (IOException ex) {
throw logger.logExceptionAsError(new UncheckedIOException(ex));
}
@@ -115,7 +54,7 @@ public T deserialize(InputStream stream, TypeReference typeReference) {
}
try {
- return mapper.readValue(stream, typeFactory.constructType(typeReference.getJavaType()));
+ return mapper.readValue(stream, typeReference.getJavaType());
} catch (IOException ex) {
throw logger.logExceptionAsError(new UncheckedIOException(ex));
}
@@ -159,124 +98,8 @@ public Mono serializeAsync(OutputStream stream, Object value) {
return Mono.fromRunnable(() -> serialize(stream, value));
}
-
@Override
public String convertMemberName(Member member) {
- if (Modifier.isTransient(member.getModifiers())) {
- return null;
- }
-
- VisibilityChecker> visibilityChecker = mapper.getVisibilityChecker();
- if (member instanceof Field) {
- Field f = (Field) member;
-
- if (f.isAnnotationPresent(JsonIgnore.class) || !visibilityChecker.isFieldVisible(f)) {
- if (f.isAnnotationPresent(JsonProperty.class)) {
- logger.info("Field {} is annotated with JsonProperty but isn't accessible to "
- + "JacksonJsonSerializer.", f.getName());
- }
- return null;
- }
-
- if (f.isAnnotationPresent(JsonProperty.class)) {
- String propertyName = f.getDeclaredAnnotation(JsonProperty.class).value();
- return CoreUtils.isNullOrEmpty(propertyName) ? f.getName() : propertyName;
- }
-
- return f.getName();
- }
-
- if (member instanceof Method) {
- Method m = (Method) member;
-
- /*
- * If the method isn't a getter, is annotated with JsonIgnore, or isn't visible to the ObjectMapper ignore
- * it.
- */
- if (!verifyGetter(m)
- || m.isAnnotationPresent(JsonIgnore.class)
- || !visibilityChecker.isGetterVisible(m)) {
- if (m.isAnnotationPresent(JsonGetter.class) || m.isAnnotationPresent(JsonProperty.class)) {
- logger.info("Method {} is annotated with either JsonGetter or JsonProperty but isn't accessible "
- + "to JacksonJsonSerializer.", m.getName());
- }
- return null;
- }
-
- String methodNameWithoutJavaBeans = removePrefix(m);
-
- /*
- * Prefer JsonGetter over JsonProperty as it is the more targeted annotation.
- */
- if (m.isAnnotationPresent(JsonGetter.class)) {
- String propertyName = m.getDeclaredAnnotation(JsonGetter.class).value();
- return CoreUtils.isNullOrEmpty(propertyName) ? methodNameWithoutJavaBeans : propertyName;
- }
-
- if (m.isAnnotationPresent(JsonProperty.class)) {
- String propertyName = m.getDeclaredAnnotation(JsonProperty.class).value();
- return CoreUtils.isNullOrEmpty(propertyName) ? methodNameWithoutJavaBeans : propertyName;
- }
-
- // If no annotation is present default to the inferred name.
- return methodNameWithoutJavaBeans;
- }
-
- return null;
- }
-
- /*
- * Only consider methods that don't have parameters and aren't void as valid getter methods.
- */
- private static boolean verifyGetter(Method method) {
- Class> returnType = method.getReturnType();
-
- return method.getParameterCount() == 0
- && returnType != void.class
- && returnType != Void.class;
- }
-
- private String removePrefix(Method method) {
- MapperConfig> config = mapper.getSerializationConfig();
-
- AnnotatedClass annotatedClass = AnnotatedClassResolver.resolve(config,
- mapper.constructType(method.getDeclaringClass()), null);
-
- AnnotatedMethod annotatedMethod = new AnnotatedMethod(null, method, null, null);
- String annotatedMethodName = annotatedMethod.getName();
-
- String name = null;
- if (USE_REFLECTION_FOR_MEMBER_NAME) {
- name = removePrefixWithReflection(config, annotatedClass, annotatedMethod, annotatedMethodName, logger);
- }
-
- if (name == null) {
- 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);
-
-
- 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;
- }
- }
-
- @SuppressWarnings("deprecation")
- private static String removePrefixWithBeanUtils(AnnotatedMethod annotatedMethod) {
- return BeanUtil.okNameForGetter(annotatedMethod, false);
+ return mapper.convertMemberName(member);
}
}
diff --git a/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializerBuilder.java b/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializerBuilder.java
index 12c7889b138ef..5256133bae3e4 100644
--- a/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializerBuilder.java
+++ b/sdk/core/azure-core-serializer-json-jackson/src/main/java/com/azure/core/serializer/json/jackson/JacksonJsonSerializerBuilder.java
@@ -3,6 +3,7 @@
package com.azure.core.serializer.json.jackson;
+import com.azure.core.implementation.jackson.ObjectMapperShim;
import com.azure.core.util.serializer.JacksonAdapter;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -17,11 +18,13 @@ public final class JacksonJsonSerializerBuilder {
* Jackson uses. This configuration is reset here by mutating the inclusion scope and null handling to use the
* default Jackson values so that JacksonJsonSerializer has less friction when this default is used.
*/
- private static final ObjectMapper DEFAULT_MAPPER = new JacksonAdapter().serializer()
- .setSerializationInclusion(JsonInclude.Include.USE_DEFAULTS)
- .setDefaultVisibility(JsonAutoDetect.Value.defaultVisibility());
+ private static final ObjectMapperShim DEFAULT_MAPPER = ObjectMapperShim
+ .createJsonMapper(ObjectMapperShim.createSimpleMapper(),
+ (mapper, innerMapper) -> mapper
+ .setSerializationInclusion(JsonInclude.Include.USE_DEFAULTS)
+ .setDefaultVisibility(JsonAutoDetect.Value.defaultVisibility()));
- private ObjectMapper objectMapper;
+ private ObjectMapperShim objectMapper;
/**
* Constructs a new instance of {@link JacksonJsonSerializer} with the configurations set in this builder.
@@ -44,7 +47,7 @@ public JacksonJsonSerializer build() {
* @return The updated JacksonJsonSerializerBuilder class.
*/
public JacksonJsonSerializerBuilder serializer(ObjectMapper objectMapper) {
- this.objectMapper = objectMapper;
+ this.objectMapper = new ObjectMapperShim(objectMapper);
return this;
}
}
diff --git a/sdk/core/azure-core-serializer-json-jackson/src/test/java/com/azure/core/serializer/json/jackson/JacksonMemberNameConverterTests.java b/sdk/core/azure-core-serializer-json-jackson/src/test/java/com/azure/core/serializer/json/jackson/JacksonMemberNameConverterTests.java
index ec9ca46b88a13..4a8d65c720e76 100644
--- a/sdk/core/azure-core-serializer-json-jackson/src/test/java/com/azure/core/serializer/json/jackson/JacksonMemberNameConverterTests.java
+++ b/sdk/core/azure-core-serializer-json-jackson/src/test/java/com/azure/core/serializer/json/jackson/JacksonMemberNameConverterTests.java
@@ -3,6 +3,7 @@
package com.azure.core.serializer.json.jackson;
+import com.azure.core.implementation.jackson.ObjectMapperShim;
import com.azure.core.util.serializer.MemberNameConverter;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -284,7 +285,7 @@ public void classConversion(T object, JacksonJsonSerializer converter, Set
--add-exports com.azure.core/com.azure.core.implementation.http=ALL-UNNAMED
--add-exports com.azure.core/com.azure.core.implementation.serializer=ALL-UNNAMED
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
--add-opens com.azure.core/com.azure.core=ALL-UNNAMED
--add-opens com.azure.core/com.azure.core.credential=ALL-UNNAMED
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/MemberNameConverterImpl.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/MemberNameConverterImpl.java
new file mode 100644
index 0000000000000..486cf24decd85
--- /dev/null
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/MemberNameConverterImpl.java
@@ -0,0 +1,199 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.implementation.jackson;
+
+import com.azure.core.util.CoreUtils;
+import com.azure.core.util.logging.ClientLogger;
+import com.azure.core.util.serializer.MemberNameConverter;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+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.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.util.BeanUtil;
+
+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;
+import java.lang.reflect.Modifier;
+
+/**
+ * Retrieves the JSON serialized property name from {@link Member}.
+ */
+final class MemberNameConverterImpl implements MemberNameConverter {
+ private static final ClientLogger LOGGER = new ClientLogger(MemberNameConverterImpl.class);
+
+ 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;
+
+ private final ObjectMapper mapper;
+
+ 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 ex) {
+ LOGGER.verbose("Failed to retrieve MethodHandles used to get naming strategy. Falling back to BeanUtils.",
+ ex);
+ }
+
+ 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;
+ }
+
+ MemberNameConverterImpl(ObjectMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public String convertMemberName(Member member) {
+ if (Modifier.isTransient(member.getModifiers())) {
+ return null;
+ }
+
+ VisibilityChecker> visibilityChecker = mapper.getVisibilityChecker();
+ if (member instanceof Field) {
+ Field f = (Field) member;
+
+ if (f.isAnnotationPresent(JsonIgnore.class) || !visibilityChecker.isFieldVisible(f)) {
+ if (f.isAnnotationPresent(JsonProperty.class)) {
+ LOGGER.info("Field {} is annotated with JsonProperty but isn't accessible to "
+ + "JacksonJsonSerializer.", f.getName());
+ }
+ return null;
+ }
+
+ if (f.isAnnotationPresent(JsonProperty.class)) {
+ String propertyName = f.getDeclaredAnnotation(JsonProperty.class).value();
+ return CoreUtils.isNullOrEmpty(propertyName) ? f.getName() : propertyName;
+ }
+
+ return f.getName();
+ }
+
+ if (member instanceof Method) {
+ Method m = (Method) member;
+
+ /*
+ * If the method isn't a getter, is annotated with JsonIgnore, or isn't visible to the ObjectMapper ignore
+ * it.
+ */
+ if (!verifyGetter(m)
+ || m.isAnnotationPresent(JsonIgnore.class)
+ || !visibilityChecker.isGetterVisible(m)) {
+ if (m.isAnnotationPresent(JsonGetter.class) || m.isAnnotationPresent(JsonProperty.class)) {
+ LOGGER.info("Method {} is annotated with either JsonGetter or JsonProperty but isn't accessible "
+ + "to JacksonJsonSerializer.", m.getName());
+ }
+ return null;
+ }
+
+ String methodNameWithoutJavaBeans = removePrefix(m);
+
+ /*
+ * Prefer JsonGetter over JsonProperty as it is the more targeted annotation.
+ */
+ if (m.isAnnotationPresent(JsonGetter.class)) {
+ String propertyName = m.getDeclaredAnnotation(JsonGetter.class).value();
+ return CoreUtils.isNullOrEmpty(propertyName) ? methodNameWithoutJavaBeans : propertyName;
+ }
+
+ if (m.isAnnotationPresent(JsonProperty.class)) {
+ String propertyName = m.getDeclaredAnnotation(JsonProperty.class).value();
+ return CoreUtils.isNullOrEmpty(propertyName) ? methodNameWithoutJavaBeans : propertyName;
+ }
+
+ // If no annotation is present default to the inferred name.
+ return methodNameWithoutJavaBeans;
+ }
+
+ return null;
+ }
+
+ /*
+ * Only consider methods that don't have parameters and aren't void as valid getter methods.
+ */
+ private static boolean verifyGetter(Method method) {
+ Class> returnType = method.getReturnType();
+
+ return method.getParameterCount() == 0
+ && returnType != void.class
+ && returnType != Void.class;
+ }
+
+ private String removePrefix(Method method) {
+ MapperConfig> config = mapper.getSerializationConfig();
+
+ AnnotatedClass annotatedClass = AnnotatedClassResolver.resolve(config,
+ mapper.constructType(method.getDeclaringClass()), null);
+
+ AnnotatedMethod annotatedMethod = new AnnotatedMethod(null, method, null, null);
+ String annotatedMethodName = annotatedMethod.getName();
+
+ String name = null;
+ if (USE_REFLECTION_FOR_MEMBER_NAME) {
+ name = removePrefixWithReflection(config, annotatedClass, annotatedMethod, annotatedMethodName);
+ }
+
+ if (name == null) {
+ name = removePrefixWithBeanUtils(annotatedMethod);
+ }
+
+ return name;
+ }
+
+ private static String removePrefixWithReflection(MapperConfig> config, AnnotatedClass annotatedClass,
+ AnnotatedMethod method, String methodName) {
+ try {
+ Object accessorNamingStrategy = FOR_POJO.invoke(GET_ACCESSOR_NAMING.invoke(config), config, annotatedClass);
+ 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;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private static String removePrefixWithBeanUtils(AnnotatedMethod annotatedMethod) {
+ return BeanUtil.okNameForGetter(annotatedMethod, false);
+ }
+}
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java
index 68d69ceb2a102..2ce8a2d8b54ad 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java
@@ -65,8 +65,7 @@ private ObjectMapperFactory() {
}
}
- public ObjectMapper createJsonMapper(ObjectMapperShim innerMapperShim) {
- ObjectMapper innerMapper = innerMapperShim.getMapper();
+ public ObjectMapper createJsonMapper(ObjectMapper innerMapper) {
ObjectMapper flatteningMapper = initializeMapperBuilder(JsonMapper.builder())
.addModule(FlatteningSerializer.getModule(innerMapper))
.addModule(FlatteningDeserializer.getModule(innerMapper))
@@ -92,7 +91,6 @@ public ObjectMapper createXmlMapper() {
.enable(FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL)
.build();
-
if (useReflectionToSetCoercion) {
try {
Object object = coersionConfigDefaults.invoke(xmlMapper);
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java
index a4da8e40e5bca..8b91e089253b8 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java
@@ -16,6 +16,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
+import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
@@ -25,6 +26,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
import java.util.function.Function;
/**
@@ -45,11 +47,13 @@ public final class ObjectMapperShim {
* Creates and configures JSON {@code ObjectMapper} capable of serializing azure.core types, with flattening and additional properties support.
*
* @param innerMapperShim inner mapper to use for non-azure specific serialization.
+ * @param configure applies additional configuration to {@code ObjectMapper}.
* @return Instance of shimmed {@code ObjectMapperShim}.
*/
- public static ObjectMapperShim createJsonMapper(ObjectMapperShim innerMapperShim) {
+ public static ObjectMapperShim createJsonMapper(ObjectMapperShim innerMapperShim, BiConsumer configure) {
try {
- ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createJsonMapper(innerMapperShim);
+ ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createJsonMapper(innerMapperShim.mapper);
+ configure.accept(mapper, innerMapperShim.mapper);
return new ObjectMapperShim(mapper);
} catch (LinkageError ex) {
throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex));
@@ -127,17 +131,12 @@ public static ObjectMapperShim createHeaderMapper() {
}
private final ObjectMapper mapper;
+ private final MemberNameConverterImpl memberNameConverter;
- private ObjectMapperShim(ObjectMapper mapper) {
- this.mapper = mapper;
- }
- /**
- * Gets wrapped {@code ObjectMapper} instance. Use with caution.
- * @return
- */
- public ObjectMapper getMapper() {
- return this.mapper;
+ public ObjectMapperShim(ObjectMapper mapper) {
+ this.mapper = mapper;
+ this.memberNameConverter = new MemberNameConverterImpl(mapper);
}
/**
@@ -254,7 +253,6 @@ public JsonNode readTree(String content) throws IOException {
* Reads JSON tree from byte array.
* @param content serialized JSON tree.
* @return {@code JsonNode} instance
- * @throws IOException
*/
public JsonNode readTree(byte[] content) throws IOException {
try {
@@ -366,6 +364,22 @@ public T deserialize(HttpHeaders headers, Type deserializedHeadersType) thro
return deserializedHeaders;
}
+ public String convertMemberName(Member member) {
+ try {
+ return memberNameConverter.convertMemberName(member);
+ } catch (LinkageError ex) {
+ throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex));
+ }
+ }
+
+ public T valueToTree(Object fromValue) {
+ try {
+ return mapper.valueToTree(fromValue);
+ } catch (LinkageError ex) {
+ throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex));
+ }
+ }
+
/*
* Helper method that gets the value for the given key from the cache.
*/
diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java b/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java
index c3cafb27759bd..71103b83faa04 100644
--- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java
+++ b/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java
@@ -14,6 +14,8 @@
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.List;
+import java.util.Objects;
+import java.util.function.BiConsumer;
import java.util.regex.Pattern;
/**
@@ -28,15 +30,17 @@ public class JacksonAdapter implements SerializerAdapter {
*/
private final ObjectMapperShim mapper;
- /**
- * An instance of {@link ObjectMapperShim} that does not do flattening.
- */
- private final ObjectMapperShim simpleMapper;
-
private final ObjectMapperShim xmlMapper;
private final ObjectMapperShim headerMapper;
+ /**
+ * Raw mappers are needed only to support deprecated simpleMapper() and
+ * serializer().
+ */
+ private ObjectMapper rawOuterMapper;
+ private ObjectMapper rawInnerMapper;
+
/*
* The lazily-created serializer for this ServiceClient.
*/
@@ -46,19 +50,55 @@ public class JacksonAdapter implements SerializerAdapter {
* Creates a new JacksonAdapter instance with default mapper settings.
*/
public JacksonAdapter() {
- this.simpleMapper = ObjectMapperShim.createSimpleMapper();
+ this((outerMapper, innerMapper) -> { });
+ }
+
+ /**
+ * Creates a new JacksonAdapter instance with Azure Core mapper settings and applies
+ * additional configuration through {@code configureSerialization} callback.
+ *
+ * {@code configureSerialization} callback provides outer and inner instances of {@link ObjectMapper}.
+ * Both of them are pre-configured for Azure serialization needs, but only outer mapper capable of
+ * flattening and populating additionalProperties. Outer mapper is used by {@code JacksonAdapter} for
+ * all serialization needs.
+ *
+ * Register modules on the outer instance to add custom (de)serializers similar to
+ * {@code new JacksonAdapter((outer, inner) -> outer.registerModule(new MyModule()))}
+ *
+ * Use inner mapper for chaining serialization logic in your (de)serializers.
+ *
+ * @param configureSerialization Applies additional configuration to outer
+ * mapper using inner mapper for module chaining.
+ */
+ public JacksonAdapter(BiConsumer configureSerialization) {
+ Objects.requireNonNull(configureSerialization, "'configureSerialization' cannot be null.");
this.headerMapper = ObjectMapperShim.createHeaderMapper();
this.xmlMapper = ObjectMapperShim.createXmlMapper();
- this.mapper = ObjectMapperShim.createJsonMapper(this.simpleMapper);
+ this.mapper = ObjectMapperShim.createJsonMapper(ObjectMapperShim.createSimpleMapper(),
+ (outerMapper, innerMapper) -> captureRawMappersAndConfigure(outerMapper, innerMapper, configureSerialization));
+ }
+
+ /**
+ * Temporary way to capture raw ObjectMapper instances, allows to support deprecated simpleMapper()
+ * and serializer()
+ */
+ private void captureRawMappersAndConfigure(ObjectMapper outerMapper, ObjectMapper innerMapper, BiConsumer configure) {
+ this.rawOuterMapper = outerMapper;
+ this.rawInnerMapper = innerMapper;
+
+ configure.accept(outerMapper, innerMapper);
}
/**
* Gets a static instance of {@link ObjectMapper} that doesn't handle flattening.
*
* @return an instance of {@link ObjectMapper}.
+ * @deprecated deprecated, use {@code JacksonAdapter(BiConsumer)} constructor to
+ * configure modules.
*/
+ @Deprecated
protected ObjectMapper simpleMapper() {
- return simpleMapper.getMapper();
+ return rawInnerMapper;
}
/**
@@ -74,14 +114,13 @@ public static synchronized SerializerAdapter createDefaultSerializerAdapter() {
}
/**
- * @return the original serializer type
+ * @return the original serializer type.
+ * @deprecated deprecated to avoid direct {@link ObjectMapper} usage in favor
+ * of using more resilient and debuggable {@link JacksonAdapter} APIs.
*/
+ @Deprecated
public ObjectMapper serializer() {
- return mapper.getMapper();
- }
-
- private ObjectMapperShim serializerShim() {
- return mapper;
+ return rawOuterMapper;
}
@Override
@@ -93,7 +132,7 @@ public String serialize(Object object, SerializerEncoding encoding) throws IOExc
if (encoding == SerializerEncoding.XML) {
return xmlMapper.writeValueAsString(object);
} else {
- return serializerShim().writeValueAsString(object);
+ return mapper.writeValueAsString(object);
}
}
@@ -106,7 +145,7 @@ public byte[] serializeToBytes(Object object, SerializerEncoding encoding) throw
if (encoding == SerializerEncoding.XML) {
return xmlMapper.writeValueAsBytes(object);
} else {
- return serializerShim() .writeValueAsBytes(object);
+ return mapper.writeValueAsBytes(object);
}
}
@@ -119,7 +158,7 @@ public void serialize(Object object, SerializerEncoding encoding, OutputStream o
if ((encoding == SerializerEncoding.XML)) {
xmlMapper.writeValue(outputStream, object);
} else {
- serializerShim().writeValue(outputStream, object);
+ mapper.writeValue(outputStream, object);
}
}
@@ -151,7 +190,7 @@ public T deserialize(String value, Type type, SerializerEncoding encoding) t
if (encoding == SerializerEncoding.XML) {
return xmlMapper.readValue(value, type);
} else {
- return serializerShim().readValue(value, type);
+ return mapper.readValue(value, type);
}
}
@@ -164,7 +203,7 @@ public T deserialize(byte[] bytes, Type type, SerializerEncoding encoding) t
if (encoding == SerializerEncoding.XML) {
return xmlMapper.readValue(bytes, type);
} else {
- return serializerShim().readValue(bytes, type);
+ return mapper.readValue(bytes, type);
}
}
@@ -178,7 +217,7 @@ public T deserialize(InputStream inputStream, final Type type, SerializerEnc
if (encoding == SerializerEncoding.XML) {
return xmlMapper.readValue(inputStream, type);
} else {
- return serializerShim().readValue(inputStream, type);
+ return mapper.readValue(inputStream, type);
}
}
diff --git a/sdk/core/azure-core/src/main/java/module-info.java b/sdk/core/azure-core/src/main/java/module-info.java
index 1c07b3dae87df..a78cf200ce96e 100644
--- a/sdk/core/azure-core/src/main/java/module-info.java
+++ b/sdk/core/azure-core/src/main/java/module-info.java
@@ -29,6 +29,9 @@
exports com.azure.core.util.serializer;
exports com.azure.core.util.tracing;
+ // TODO temporary until we find final shape of ObjectMapper shimming APIs
+ exports com.azure.core.implementation.jackson to com.azure.core.management, com.azure.core.serializer.json.jackson;
+
// exporting some packages specifically for Jackson
opens com.azure.core.http to com.fasterxml.jackson.databind;
opens com.azure.core.models to com.fasterxml.jackson.databind;
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java
index 0634e84083720..3c8e70c4543ec 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java
@@ -4,7 +4,7 @@
package com.azure.core.implementation.jackson;
import com.azure.core.util.serializer.JacksonAdapter;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.azure.core.util.serializer.SerializerEncoding;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -20,12 +20,12 @@
* Tests for {@link DateTimeDeserializer}.
*/
public class DateTimeDeserializerTests {
- private static final ObjectMapper MAPPER = new JacksonAdapter().serializer();
+ private static final JacksonAdapter MAPPER = new JacksonAdapter();
@ParameterizedTest
@MethodSource("deserializeOffsetDateTimeSupplier")
public void deserializeJson(String dateTimeJson, OffsetDateTime expected) throws IOException {
- assertEquals(expected, MAPPER.readValue(dateTimeJson, OffsetDateTime.class));
+ assertEquals(expected, MAPPER.deserialize(dateTimeJson, OffsetDateTime.class, SerializerEncoding.JSON));
}
private static Stream deserializeOffsetDateTimeSupplier() {
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/MemberNameConverterImplTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/MemberNameConverterImplTests.java
new file mode 100644
index 0000000000000..ba4a97a600165
--- /dev/null
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/MemberNameConverterImplTests.java
@@ -0,0 +1,166 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.implementation.jackson;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class MemberNameConverterImplTests {
+ @Test
+ public void fieldsWithJsonProperty() throws NoSuchFieldException {
+ final Field publicFieldWithAnnotationAndValue = Foo.class.getDeclaredField("publicFieldWithAnnotationAndValue");
+ final Field publicFieldWithAnnotationNoValue = Foo.class.getDeclaredField("publicFieldWithAnnotationNoValue");
+ final Field privateFieldWithAnnotation = Foo.class.getDeclaredField("privateFieldWithAnnotation");
+
+ MemberNameConverterImpl memberNameConverter = new MemberNameConverterImpl(new ObjectMapper());
+
+ assertEquals("public-field-with-annotation-and-value", memberNameConverter.convertMemberName(publicFieldWithAnnotationAndValue));
+ assertEquals("publicFieldWithAnnotationNoValue", memberNameConverter.convertMemberName(publicFieldWithAnnotationNoValue));
+ assertNull(memberNameConverter.convertMemberName(privateFieldWithAnnotation));
+ }
+
+ @Test
+ public void methodsWithJsonGetter() throws NoSuchMethodException {
+ final Method getPublicWithAnnotationAndValue = Foo.class.getDeclaredMethod("getPublicWithAnnotationAndValue");
+ final Method getPublicWithAnnotationNoValue = Foo.class.getDeclaredMethod("getPublicWithAnnotationNoValue");
+ final Method publicWithAnnotationNoPrefix = Foo.class.getDeclaredMethod("publicWithAnnotationNoPrefix");
+ final Method isPublicWithAnnotationString = Foo.class.getDeclaredMethod("isPublicWithAnnotationString");
+ final Method isPublicWithAnnotationBoolean = Foo.class.getDeclaredMethod("isPublicWithAnnotationBoolean");
+ final Method getPrivateWithAnnotation = Foo.class.getDeclaredMethod("getPrivateWithAnnotation");
+ final Method getVoidWithAnnotation = Foo.class.getDeclaredMethod("getVoidWithAnnotation");
+
+ MemberNameConverterImpl memberNameConverter = new MemberNameConverterImpl(new ObjectMapper());
+
+ assertEquals("public-getter-with-annotation-and-value", memberNameConverter.convertMemberName(getPublicWithAnnotationAndValue));
+ assertEquals("publicWithAnnotationNoValue", memberNameConverter.convertMemberName(getPublicWithAnnotationNoValue));
+ assertEquals("publicWithAnnotationBoolean", memberNameConverter.convertMemberName(isPublicWithAnnotationBoolean));
+ assertNull(memberNameConverter.convertMemberName(publicWithAnnotationNoPrefix));
+ assertNull(memberNameConverter.convertMemberName(isPublicWithAnnotationString));
+ assertNull(memberNameConverter.convertMemberName(getPrivateWithAnnotation));
+ assertNull(memberNameConverter.convertMemberName(getVoidWithAnnotation));
+ }
+
+ @Test
+ public void fieldNoJsonProperty() throws NoSuchFieldException {
+ final Field publicIgnoredField = Foo.class.getDeclaredField("publicIgnoredField");
+ final Field publicNoAnnotationField = Foo.class.getDeclaredField("publicNoAnnotationField");
+ final Field privateField = Foo.class.getDeclaredField("privateField");
+
+ MemberNameConverterImpl memberNameConverter = new MemberNameConverterImpl(new ObjectMapper());
+
+ assertEquals("publicNoAnnotationField", memberNameConverter.convertMemberName(publicNoAnnotationField));
+ assertNull(memberNameConverter.convertMemberName(publicIgnoredField));
+ assertNull(memberNameConverter.convertMemberName(privateField));
+ }
+
+ @Test
+ public void methodsNoJsonGetter() throws NoSuchMethodException {
+ final Method getPublicIgnored = Foo.class.getDeclaredMethod("getPublicIgnored");
+ final Method getPublicNoAnnotation = Foo.class.getDeclaredMethod("getPublicNoAnnotation");
+ final Method publicNoAnnotationNoPrefix = Foo.class.getDeclaredMethod("publicNoAnnotationNoPrefix");
+ final Method isPublicNoAnnotationString = Foo.class.getDeclaredMethod("isPublicNoAnnotationString");
+ final Method isPublicNoAnnotationBoolean = Foo.class.getDeclaredMethod("isPublicNoAnnotationBoolean");
+ final Method getNoAnnotationVoid = Foo.class.getDeclaredMethod("getNoAnnotationVoid");
+ final Method getNoAnnotationPrivate = Foo.class.getDeclaredMethod("getNoAnnotationPrivate");
+
+ MemberNameConverterImpl memberNameConverter = new MemberNameConverterImpl(new ObjectMapper());
+
+ assertEquals("publicNoAnnotation", memberNameConverter.convertMemberName(getPublicNoAnnotation));
+ assertEquals("publicNoAnnotationBoolean", memberNameConverter.convertMemberName(isPublicNoAnnotationBoolean));
+ assertNull(memberNameConverter.convertMemberName(getPublicIgnored));
+ assertNull(memberNameConverter.convertMemberName(publicNoAnnotationNoPrefix));
+ assertNull(memberNameConverter.convertMemberName(isPublicNoAnnotationString));
+ assertNull(memberNameConverter.convertMemberName(getNoAnnotationVoid));
+ assertNull(memberNameConverter.convertMemberName(getNoAnnotationPrivate));
+ }
+
+ private static class Foo {
+ @JsonProperty(value = "public-field-with-annotation-and-value")
+ public String publicFieldWithAnnotationAndValue;
+
+ @JsonProperty
+ public String publicFieldWithAnnotationNoValue;
+
+ @JsonIgnore
+ public String publicIgnoredField;
+
+ public String publicNoAnnotationField;
+
+ @JsonProperty
+ private String privateFieldWithAnnotation = null;
+
+ private String privateField = null;
+
+ @JsonGetter(value = "public-getter-with-annotation-and-value")
+ public String getPublicWithAnnotationAndValue() {
+ return "foo";
+ }
+
+ @JsonGetter
+ public String getPublicWithAnnotationNoValue() {
+ return "foo";
+ }
+
+ @JsonGetter
+ public void getVoidWithAnnotation() {
+ }
+
+ @JsonGetter
+ public String publicWithAnnotationNoPrefix() {
+ return "foo";
+ }
+
+ @JsonGetter
+ public String isPublicWithAnnotationString() {
+ return "foo";
+ }
+
+ @JsonGetter
+ public boolean isPublicWithAnnotationBoolean() {
+ return true;
+ }
+
+ @JsonIgnore
+ public String getPublicIgnored() {
+ return "foo";
+ }
+
+ public String getPublicNoAnnotation() {
+ return "foo";
+ }
+
+ public String publicNoAnnotationNoPrefix() {
+ return "foo";
+ }
+
+ public String isPublicNoAnnotationString() {
+ return "foo";
+ }
+
+ public boolean isPublicNoAnnotationBoolean() {
+ return true;
+ }
+
+ public void getNoAnnotationVoid() {
+ }
+
+ @JsonGetter
+ private String getPrivateWithAnnotation() {
+ return "foo";
+ }
+
+ private String getNoAnnotationPrivate() {
+ return "foo";
+ }
+ }
+}
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/ObjectMapperShimTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/ObjectMapperShimTests.java
new file mode 100644
index 0000000000000..302de70b12c3f
--- /dev/null
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/ObjectMapperShimTests.java
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.implementation.jackson;
+
+import com.azure.core.util.serializer.JacksonAdapter;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ObjectMapperShimTests {
+ @Test
+ public void testConfigure() {
+ final ObjectMapper innerMapper = new ObjectMapper();
+ final ObjectMapperShim innerShim = new ObjectMapperShim(innerMapper);
+
+ final AtomicReference configureIsCalled = new AtomicReference<>(false);
+ ObjectMapperShim.createJsonMapper(innerShim, (outer, inner) -> {
+ assertNotNull(outer);
+ assertSame(innerMapper, inner);
+ configureIsCalled.set(true);
+ });
+
+ assertTrue(configureIsCalled.get());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testConfigureJacksonAdapter() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ final AtomicReference configureIsCalled = new AtomicReference<>(false);
+ final AtomicReference outerMapper = new AtomicReference<>(null);
+ final AtomicReference innerMapper = new AtomicReference<>(null);
+
+ final JacksonAdapter adapter = new JacksonAdapter((outer, inner) -> {
+ outerMapper.set(outer);
+ innerMapper.set(inner);
+ assertNotNull(outer);
+ assertNotNull(inner);
+ configureIsCalled.set(true);
+ });
+
+ assertTrue(configureIsCalled.get());
+ assertSame(outerMapper.get(), adapter.serializer());
+
+ // check protected simpleMapper getter
+ Method method = JacksonAdapter.class.getDeclaredMethod("simpleMapper");
+ method.setAccessible(true);
+ assertSame(innerMapper.get(), method.invoke(adapter));
+ }
+}
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java
index 45e534e00463c..f895b28fb0e2e 100644
--- a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java
@@ -16,14 +16,7 @@
* Tests for {@link Option} that can represent tri-sate (non-null-value, null-value, or no-value).
*/
public class OptionSerializerTests {
- private static final JacksonAdapter ADAPTER;
-
- static {
- JacksonAdapter adapter = new JacksonAdapter();
- adapter.serializer().registerModule(new OptionModule());
-
- ADAPTER = adapter;
- }
+ private static final JacksonAdapter ADAPTER = new JacksonAdapter((outerMapper, innerMapper) -> outerMapper.registerModule(new OptionModule()));
@Test
public void canSerializeExplicitNull() throws IOException {
diff --git a/sdk/digitaltwins/azure-digitaltwins-core/pom.xml b/sdk/digitaltwins/azure-digitaltwins-core/pom.xml
index c3bdf25eece74..8e39d35eea2e2 100644
--- a/sdk/digitaltwins/azure-digitaltwins-core/pom.xml
+++ b/sdk/digitaltwins/azure-digitaltwins-core/pom.xml
@@ -146,6 +146,7 @@
--add-exports com.azure.core/com.azure.core.implementation.http=ALL-UNNAMED
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
--add-opens com.azure.digitaltwins.core/com.azure.digitaltwins.core=ALL-UNNAMED
diff --git a/sdk/eventgrid/azure-messaging-eventgrid/pom.xml b/sdk/eventgrid/azure-messaging-eventgrid/pom.xml
index 830364b7d0f10..080ba1afd509c 100644
--- a/sdk/eventgrid/azure-messaging-eventgrid/pom.xml
+++ b/sdk/eventgrid/azure-messaging-eventgrid/pom.xml
@@ -141,6 +141,7 @@
3.0.0-M3
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
--add-opens com.azure.messaging.eventgrid/com.azure.messaging.eventgrid=ALL-UNNAMED
--add-opens com.azure.messaging.eventgrid/com.azure.messaging.eventgrid.implementation=ALL-UNNAMED
diff --git a/sdk/monitor/azure-monitor-query/pom.xml b/sdk/monitor/azure-monitor-query/pom.xml
index 9c144575d099d..e732e8be79012 100644
--- a/sdk/monitor/azure-monitor-query/pom.xml
+++ b/sdk/monitor/azure-monitor-query/pom.xml
@@ -27,6 +27,11 @@
https://github.com/Azure/azure-sdk-for-java
+
+
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
+
+
com.azure
diff --git a/sdk/purview/azure-analytics-purview-catalog/pom.xml b/sdk/purview/azure-analytics-purview-catalog/pom.xml
index ef85d5297089e..e0b0658425b1c 100644
--- a/sdk/purview/azure-analytics-purview-catalog/pom.xml
+++ b/sdk/purview/azure-analytics-purview-catalog/pom.xml
@@ -31,6 +31,9 @@
true
+
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
+
diff --git a/sdk/purview/azure-analytics-purview-scanning/pom.xml b/sdk/purview/azure-analytics-purview-scanning/pom.xml
index 46b39df6b1490..862dc73b94263 100644
--- a/sdk/purview/azure-analytics-purview-scanning/pom.xml
+++ b/sdk/purview/azure-analytics-purview-scanning/pom.xml
@@ -31,6 +31,9 @@
true
+
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
+
diff --git a/sdk/search/azure-search-documents/pom.xml b/sdk/search/azure-search-documents/pom.xml
index f19d75681dcfd..f46f05219504d 100644
--- a/sdk/search/azure-search-documents/pom.xml
+++ b/sdk/search/azure-search-documents/pom.xml
@@ -107,6 +107,7 @@
--add-exports com.azure.core/com.azure.core.implementation.http=ALL-UNNAMED
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
--add-opens com.azure.core/com.azure.core.util=ALL-UNNAMED
--add-opens com.azure.search.documents/com.azure.search.documents=ALL-UNNAMED
--add-opens com.azure.search.documents/com.azure.search.documents.indexes=ALL-UNNAMED
diff --git a/sdk/translation/azure-ai-documenttranslator/pom.xml b/sdk/translation/azure-ai-documenttranslator/pom.xml
index 7982fb37847cd..c76ba51285d24 100644
--- a/sdk/translation/azure-ai-documenttranslator/pom.xml
+++ b/sdk/translation/azure-ai-documenttranslator/pom.xml
@@ -32,6 +32,9 @@
true
+
+ --add-exports com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED
+