Skip to content

Conversation

@Avery-Dunn
Copy link
Contributor

This PR begins the process of replacing the com.fasterxml.jackson.core dependency with azure-json, #909

There are a lot of added and deleted lines, but for the most part the changes are:

  • Adjust classes to implement azure-json's JsonSerializable interface
  • Remove jackson-databind's JSON annotations from those classes
  • Add a new method to JsonHelper which converts a JSON string to a class which implements the JsonSerializable interface

To help keep the PR focused and lower the risk of a breaking change in behavior, this first PR covers various package-private classes and methods. Follow-up PRs will finish the job of removing com.fasterxml.jackson.core, as well as use RevAPI to ensure no changes are made to the API

@Avery-Dunn Avery-Dunn requested a review from a team as a code owner March 4, 2025 23:20
//This method is used to convert a JSON string to an object which implements the JsonSerializable interface from com.azure.json
static <T extends JsonSerializable<T>> T convertJsonStringToJsonSerializableObject(String jsonResponse, Class<T> tClass) {
try (JsonReader jsonReader = JsonProviders.createReader(jsonResponse)) {
return (T) tClass.getDeclaredMethod("fromJson", JsonReader.class).invoke(null, jsonReader);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is all this reflection ok for apps consuming our libs? In .NET at least, reflection is frawned upon because it stops the AOT linker from doing its job. It's also slow.

JSON libraries prefer to use a "source generation" approach which avoids reflection and is faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first version I had simply had the same try/catch clause in a lot of places, and to avoid duplicate code I tried this approach.

However, ease of debugging is a concern (that's part of the reason we're even doing this), and since this JSON parsing could happen on every request it might slow them down. I'll do some more research/testing to see if there's a better approach (sending a lambda?), and if not I'll go back to directly calling that "fromJson" method in various places.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to remove reflection by making a few changes here:

  1. Change the method parameters to:
static <T extends JsonSerializable<T>> T convertJsonStringToJsonSerializableObject(String jsonResponse, ReadValueCallback<JsonReader, T> readFunction)

You know at calling time what T will be.

  1. Change the try-with-resources block to:
try (JsonReader jsonReader = JsonProviders.createReader(jsonResponse)) {
    return readFunction.read(jsonReader);
}
  1. Change the caller to:
JsonHelper.convertJsonStringToJsonSerializableObject(instanceDiscoveryJson, AadInstanceDiscoveryResponse::fromJson);
JsonHelper.convertJsonStringToJsonSerializableObject(response.body(), ManagedIdentityResponse::fromJson);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, I made these changes in the latest commit.

Definitely easier to read, and just from a couple quick tests runs about twice as fast (0-2 milliseconds instead of 2-4)

@JsonProperty(value = "expires_on") String expiresOn) {
this.accessToken = token;
this.expiresOn = expiresOn;
public static ManagedIdentityResponse fromJson(JsonReader jsonReader) throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jackson seems more elegant / readable with the attribute approach...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely more elegant, there's a reason it's by far the most popular JSON-related library on Maven: https://mvnrepository.com/open-source/json-libraries

But to get rid of 3rd-party dependencies there's not much choice, azure-json is at least used by most other Azure products: https://mvnrepository.com/artifact/com.azure/azure-json/usages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jackson Databind is much cleaner, and in some cases clearer, but it does require a lot of reflection to be used by Jackson. This code here is effectively equivalent to what you'd see if you were using Jackson Core which doesn't have all the reflection handling.

reader.nextToken();
switch (fieldName) {
case "ver":
response.version = Float.parseFloat(reader.getString());
Copy link

@alzimmermsft alzimmermsft Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonReader has getFloat, I'd recommend using that instead. Unless you need special handling if ver is actually a String JSON node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally used getFloat, however it gave me the following error: Current token (VALUE_STRING) not numeric, can not use numeric value accessors at [Source: (String)"{"ver":"1.0", ...

I assumed it was because the JSON we were receiving was a string of "1.0", though at least according to the specs shouldn't it be able to parse a number or a string? https://learn.microsoft.com/en-us/java/api/com.azure.json.jsonreader?view=azure-java-stable#com-azure-json-jsonreader-getfloat()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonReader uses a very literal interpretation of the JSON spec where it won't perform implicit conversions to numbers or booleans if the underlying value is a JSON string.

@Avery-Dunn Avery-Dunn merged commit a990b4a into dev Mar 17, 2025
5 checks passed
@Avery-Dunn Avery-Dunn deleted the avdunn/jackson-databind-removal branch June 3, 2025 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants