-
Notifications
You must be signed in to change notification settings - Fork 2k
Azure Json Migration
com.azure:azure-json
is a library that defines interfaces for reading and writing JSON using streaming APIs, or in other words,
APIs where you explicitly define the behavior of the JSON being written. For example, a simple object with a single string property
"hello":
jsonWriter.writeStartObject()
.writeStringField("hello", "world")
.writeEndObject();
com.azure:azure-json
contains no external dependencies and provides a default implementation of the reader and writer interfaces.
-
azure-json
contains no external dependencies, removing possible dependency conflicts with popular JSON serialization libraries such as Jackson and GSON. - Stream serialization is easier to debug and reason about behavior as there isn't configurations hidden at construction time of the reader and writer nor does it need to perform reflective accesses to objects being deserialized.
- Reduces the amount of reflection and reflective access permissions, limiting possible access denied exceptions if the module system
is configured incorrectly or a
SecurityManager
is being used to limit permissions during runtime. - Possible reduction in JAR sizes and potential performance gains.
- Since reading and writing logic are interfaces the backing implementation could conform to the environment running the Azure SDK code. For example, you can use a GSON or Jackson implementation instead of the default.
Migration to azure-json
is dependent on the library being migrated. Code generated libraries will require Autorest configuration changes
to enable generating code with azure-json
. Handwritten libraries, or portions of code generated libraries that were handwritten, will
require manual intervention.
As part of this migration, you'll be implementing (either through code generation or manually) JsonSerializable<T>
on all your models and
removing the ability for Jackson Databind to reflectively access and serialize your models (functionality replaced by JsonSerializable<T>
).
Start by adding the following dependency to your project's pom.xml
.
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-json</artifactId>
<version>1.1.0</version> <!-- {x-version-update;com.azure:azure-json;dependency} -->
</dependency>
Then add the following requires to your project's module-info.java
.
requires com.azure.json;
Also, in the project's module-info.java
, ensure that all packages that currently opens to
com.fasterxml.jackson.databind
opens to
com.azure.core
and they remove the opens to
com.fasterxml.jackson.databind
. This will ensure two things:
-
azure-core
will be able to deserialize your models usingJsonSerializable.fromJson(JsonReader)
that is required for all serializable models usingazure-json
. - Result in a reflection failure if Jackson Databind continues to be used to serialize or deserialize your models, which is being removed as part of this migration.
For example, if you had:
opens com.azure.sdk.models to com.fasterxml.jackson.databind;
opens com.azure.sdk.implementation to com.fasterxml.jackson.databind, com.azure.core;
it becomes:
opens com.azure.sdk.models to com.azure.core;
opens com.azure.sdk.implementation to com.azure.core;
If your POM has the property javaModulesSurefireArgLine
configured, remove any --add-exports
, --add-opens
, and --add-reads
using
com.fasterxml.jackson.databind
.
Updating code generation may not apply to every library.
In your Autorest configuration, enabling azure-json
is just adding the configuration stream-style-serialization: true
. In a future release
of Autorest this will become the default configuration.
Additionally, at this time if you want, add the use
configuration to your Autorest configurations. This configures which version of Autorest
Java to use in code generation without having to pass it each time in the command line using --use=<version>
. View here for the latest versions
of Autorest Java: https://github.com/Azure/autorest.java/releases. An example of the configuration is use: '@autorest/[email protected]'
.
Additionally, you can view the template project's Swagger configuration for a good set of default configurations to use:
TypeSpec configurations are managed centrally in the Azure REST API Specs repository. Similar to Swagger, stream style serialization is enabled in newer versions of TypeSpec Java (which is universally upgraded in the Azure SDK for Java repository, so there aren't any requirements to change a generator tool version).
At this time, the only scenario where stream style serialization code generation is disabled with TypeSpec is when the configuration in the REST API
specs repo contains an explicit stream-style-serialization: false
. If that happens, you'll need to file a PR against the configuration to remove that
configuration line and update the tsp-location.yaml
's commit
SHA to the merged PR (for testing validation you can point to the commit SHA to make
sure everything is working correctly, then point to the merged SHA afterwards).
Updating handwritten code requires removing usage of Jackson as much as possible, ideally completely. Unfortunately, this step isn't as quick, but use the following as ways to help scope work.
- In your project search for all references of
com.fasterxml.jackson
. This will find spots where Jackson is being used. - Follow the deserialization logic used by generated code.
If you have questions reach out to the Azure SDK for Java team, we'll be more than happy to help.
Code using Jackson annotations.
public final class FileSystemEncryptionScopeOptions {
/*
* Optional. Version 2021-06-08 and later. Specifies the default
* encryption scope to set on the container and use for all future writes.
*/
@JsonProperty(value = "DefaultEncryptionScope")
private String defaultEncryptionScope;
/*
* Optional. Version 2021-06-08 and newer. If true, prevents any request
* from specifying a different encryption scope than the scope set on the
* container.
*/
@JsonProperty(value = "EncryptionScopeOverridePrevented")
private Boolean encryptionScopeOverridePrevented;
}
Code migrated to azure-json.
public final class FileSystemEncryptionScopeOptions implements JsonSerializable<FileSystemEncryptionScopeOptions> {
/*
* Optional. Version 2021-06-08 and later. Specifies the default
* encryption scope to set on the container and use for all future writes.
*/
private String defaultEncryptionScope;
/*
* Optional. Version 2021-06-08 and newer. If true, prevents any request
* from specifying a different encryption scope than the scope set on the
* container.
*/
private Boolean encryptionScopeOverridePrevented;
@Override
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
jsonWriter.writeStartObject();
jsonWriter.writeStringField("DefaultEncryptionScope", defaultEncryptionScope);
jsonWriter.writeBooleanField("EncryptionScopeOverridePrevented", encryptionScopeOverridePrevented);
jsonWriter.writeEndObject();
return jsonWriter;
}
/**
* Reads a JSON stream into a {@link FileSystemEncryptionScopeOptions}.
*
* @param jsonReader The {@link JsonReader} being read.
* @return The {@link FileSystemEncryptionScopeOptions} that the JSON stream represented, or null if it pointed to JSON null.
* @throws IOException If an I/O error occurs.
*/
public static FileSystemEncryptionScopeOptions fromJson(JsonReader jsonReader) throws IOException {
return jsonReader.readObject(reader -> {
FileSystemEncryptionScopeOptions fileSystemEncryptionScopeOptions = new FileSystemEncryptionScopeOptions();
while (reader.nextToken() != JsonToken.END_OBJECT) {
String fieldName = reader.getFieldName(); // Get the name of the field.
reader.nextToken(); // Progress to the value.
if ("DefaultEncryptionScope".equals(fieldName)) {
fileSystemEncryptionScopeOptions.defaultEncryptionScope = reader.getString();
} else if ("EncryptionScopeOverridePrevented".equals(fieldName)) {
fileSystemEncryptionScopeOptions.encryptionScopeOverridePrevented = reader.getNullable(JsonReader::getBoolean);
} else {
// Skip unknown values.
// If the type supported additional properties, this is where they would be handled.
reader.skipChildren();
}
}
return fileSystemEncryptionScopeOptions;
});
};
}
Note: toJson
doesn't need to call JsonWriter.flush()
as that is handled by the caller.
There are two things that may require updating after completing code migration.
- Stream serialization is more verbose and is lines of code instead of annotations like Jackson Databind uses. Your code coverage will likely drop after migration, meaning either more testing needs to be added to missed lines or required code coverage needs to be reduced.
- RevApi considers removal of Jackson annotations to be a breaking change. You'll need to update the RevApi suppressions file to ignore these API breaks. Here is an example of current suppressions, they should be easily copied and updated to fit your needs:
{
"regex": true,
"code": "java\\.annotation\\.removed",
"old": ".*? com\\.azure\\.ai\\.metricsadvisor\\.(administration\\.)?models.*",
"justification": "Removing Jackson annotations from Metrics Advisor in transition to stream-style."
},
{
"regex": true,
"code": "java\\.annotation\\.removed",
"old": ".*? com\\.azure\\.messaging\\.eventgrid\\.systemevents.*",
"justification": "Removing Jackson annotations from EventGrid in transition to stream-style."
}
Finally, just run your test suite, both in playback and live testing, to make sure there are no regressions or bugs introduced as part of migration.
- Frequently Asked Questions
- Azure Identity Examples
- Configuration
- Performance Tuning
- Android Support
- Unit Testing
- Test Proxy Migration
- Azure Json Migration
- New Checkstyle and Spotbugs pattern migration
- Protocol Methods
- TypeSpec-Java Quickstart
- Getting Started Guidance
- Adding a Module
- Building
- Writing Performance Tests
- Working with AutoRest
- Deprecation
- BOM guidelines
- Release process
- Access helpers