-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Serialization
- Customizable serialization
- Default serialization
- Default configuration settings
- Serializable models included in azure-core
- Misc assumptions
Azure Core offers the ObjectSerializer and JsonSerializer interfaces as a standard way to handle serialization and deserialization of specific serialization formats. There is a default implementation of these interfaces built into Azure Core, but users and Azure SDKs can specify their own through service provider interface (SPI). The SPI allows for the serialization layer to be plug-able and removes dependencies on given implementations. Here are the Azure Core models that support serializer customization
If you're a user looking to provide your serializer implementation, please refer to Custom JSON serializer.
If you're Azure Core developer and want to support customer serializers, use ObjectSerializer abstraction and resolve instance with JsonSerializerProviders.createInstance (or AvroSerializerProviders)
-
com.azure.core.util.serializer.ObjectSerializer- Generic interface for (de)serializing objects. Abstracts format-specific serializers. -
com.azure.core.util.serializer.JsonSerializer- Generic interface covering basic JSON (de)serialization methods. -
com.azure.core.util.serializer.JsonSerializerProvider- extension point - resolvesJsonSerializerimplementation in runtime.
-
Package azure-core-serializer-json-jackson:
com.azure.core.serializer.json.jackson.JacksonJsonSerializer-JsonSerializerimplementation of theJsonSerializerdefaulting to Azure Core internal implementation. -
Package azure-core-serializer-json-gson:
com.azure.core.serializer.json.gson.GsonJsonSerializer-JsonSerializerbased on Gson. Intended for consumers. -
Package azure-core-serializer-avro-apache - includes experimental
com.azure.core.serializer.avro.apache.ApacheAvroSerializer-AvroSerializerbased on apache apache (de)serializer. Intended for consumers.
All of them come with a builder and provider.
If you're a library developer and want users to be able to provide their own serializer - take a dependency on one of the implementation packages. We recommend using azure-core-serializer-json-jackson for Json and azure-core-serializer-avro-apache for Avro.
If you don't want users to be able to replace serializers and can't use the default one, you can create your implementation and instantiate it directly without calling into JsonSerializerProviders.createInstance.
-
Creation:: Get a new instance with
JsonSerializerProviders.createInstance(useDefaultIfAbsent).- If you don't want to use the default implementation, pass
useDefaultIfAbsent=false, make sure to include a custom one (e.g. one inazure-core-serializer-json-jackson) and configure it
- If you don't want to use the default implementation, pass
-
Changing configuration:
- it's not possible to change serialization configuration for instance obtained with
JsonSerializerProviders.createInstanceunless you fully replace the serializer. - Azure SDK developers should use
JacksonJsonSerializerBuilder.serializermethod to pass customizedObjectMapper. Consumers won't be able to replace or customize this instance.
- it's not possible to change serialization configuration for instance obtained with
Please refer to the reference docs for more details.
public final class User {
private static final JsonSerializer SERIALIZER = JsonSerializerProviders.createInstance(true);
public User() {}
@JsonProperty
public String firstName;
@JsonProperty
public String lastName;
public static User fromString(String str) {
return SERIALIZER.deserializeFromBytes(str.getBytes(StandardCharsets.UTF_8), TypeReference.createInstance(User.class));
}
public String toString() {
return new String(SERIALIZER.serializeToBytes(this), StandardCharsets.UTF_8);
}
}If you don't need to support customizable serializers for the model, but want to configure some settings on the serializer, please use JacksonJsonSerializerBuilder from azure-core-serializer-json-jackson. A similar approach can be used with ApacheAvroSerializerBuilder or GsonJsonSerializerBuilder.
public final class User {
private static final JsonSerializer SERIALIZER = new JacksonJsonSerializerBuilder()
.serializer(new ObjectMapper().registerModule(
new SimpleModule().addSerializer(User.class, new UserSerializer())
.addDeserializer(User.class, new UserDeserializer())))
.build();
public User() {}
@JsonProperty
public String firstName;
@JsonProperty
public String lastName;
public static User fromString(String str) {
return SERIALIZER.deserializeFromBytes(str.getBytes(StandardCharsets.UTF_8), TypeReference.createInstance(User.class));
}
public String toString() {
return new String(SERIALIZER.serializeToBytes(this), StandardCharsets.UTF_8);
}
}If you're an Azure Core or Azure SDK developer and don't want customers to provide their own serializer, use SerializerAdapter abstraction and JacksonAdapter implementation.
-
com.azure.core.util.serializer.SerializerAdapter- interface for all serialization (Json and XML). -
com.azure.core.util.serializer.JacksonAdapter- implementsSerializerAdapter- default (de)serialization (json and XML) implementation.
-
Creation:: Create new instance with
JacksonAdapter()constructor or use staticcreateDefaultSerializerAdapter()method that returns a singleton instance. -
Changing configuration:
JacksonAdapter.serializer()returns underlyingcom.fasterxml.jackson.databind.ObjectMapperso you may add or override default configuration. WARNING: please avoid changing configuration on the singleton instance returned bycreateDefaultSerializerAdapter- Azure Core functionality depends on it. -
Serialization methods : Please refer to the reference docs for more details. Some notable details:
serializeRaw,serializeList,serializeIterable,<T> deserialize(HttpHeaders headers, Type deserializedHeadersType)only support Json.
Here is the default configuration applied on json/xml serialization in Azure Core.
Applies to:
-
JacksonAdapter- default implementation. Note: if there is no custom provider on the classpath,JsonSerializerProviders.createInstance(/*useDefaultIfAbsent*/ true)resolves to serializer that wrapsJacksonAdapter, i.e. below configuration would apply. -
JacksonJsonSerializer- (unless customObjectMapperis used) with some exceptions mentioned below.
Configuration does not apply to GsonJsonSerializer, JacksonAvroSerializer or ApacheAvroSerializer - they use corresponding underlying package (jackson, gson, apache avro) defaults unless customized.
Note: Jackson behavior regarding null/empty (de)serialization edge cases depends on version and subject to change (especially in xml). Please support variety of cases and avoid depending on specific behavior.
JacksonJsonSerializer uses Jackson defaults (unless customized).
- Null fields/properties:
- serialization:
- json:
-
JacksonAdapter: no -
JacksonJsonSerializer: yes, as null.
-
- xml: no
- json:
- deserialization: as nulls
- serialization:
- Empty strings
- serialization:
- json: empty string
- xml: empty element (
<a></a>)
- deserialization to string:
- json: as empty string
- xml:
- empty element (
<a></a>) - empty string - self-closing tag (
<a/>) - null
- empty element (
- deserialization to an arbitrary object:
- json: as null
- xml:
- empty element (
<a></a>) - null - self-closing tag (
<a/>) - null
- empty element (
- serialization:
- json: sensitive
- xml: not sensitive
- serialization: yes
- deserialization: not required
- Null arrays/collections
- serialization:
- json: no
- xml: no
- deserialization
- json: no element/element with null value -
null(map/iterable/list/array) - xml:
- no element:
null(map/iterable/list/array) - self-closing tag (
<a/>) -null(map/iterable/list/array) since Jackson 2.12.4
- no element:
- json: no element/element with null value -
- serialization:
- Empty arrays/collections
- serialization:
- json: yes (as empty element)
- xml: yes as a self-closing tag (
<a/>)- empty
Iterableis not serialized at all (TODO)
- empty
- deserialization
- json: empty array/collection
- xml: there is no way to express empty array with XML (with wrapping turned off)
- empty element (
<a></a>), depends on type:- collection of
Strings:[""] - array of not-
Strings:[null](because ofDeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAYsetting) - list of not-
Strings:null
- collection of
- self-closing tag (
<a/>) -nullsince Jackson 2.12.4
- empty element (
- serialization:
- Single element
- serialization:
- json: as array
- xml: as one element with value: a=[1] ->
<a>1</a>
- deserialization
- json:
- array: array
- number: array
- xml
- one element with value
<a>1</a>: 1-element array - empty element (
<a></a>), depends on type:- collection of
Strings:[""] - array of not-
Strings:[null](because ofDeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAYsetting that works for arrays only) - list of not-
Strings:null
- collection of
- one element with value
- json:
- serialization:
- Multiple elements:
- json: array/collection with multiple elements
- xml: no wrapping for serialization, array/collection with multiple elements
- Byte Arrays
- serialization:
- json/xml: base64-encoded string
[1,2,3,4,5,6,7,8]->AQIDBAUGBwg=- jackson default: array
- json/xml: base64-encoded string
- deserialization: as array (base64-string serialization is not supported)
- serialization:
Annotated properties/fields are always serialized regardless of visibility.
For auto-detected fields/properties/etc JacksonAdapter and JacksonJsonSerializer have different settings.
JacksonJsonSerializer uses jackson defaults (unless customized).
-
Not-annotated fields:
-
JacksonAdapter: serialized regardless of visibility -
JacksonJsonSerializer: public-only not-annotated fields
-
-
Not-annotated properties (getter + setter):
-
JacksonAdapter: none -
JacksonJsonSerializer: public-only getters, any setters
-
-
Not-annotated Is-Getter
-
JacksonAdapter: none -
JacksonJsonSerializer: public-only
-
-
Creators: public-only.
- Unknown properties: ignored
- Empty beans serialization (serialization of classes that are not serializable): empty object (
{}) - Exception handling:
-
JacksonAdapter: Jackson exceptions are not handled and not logged. -
JacksonJsonSerializer: allIOExceptions (includingMismatchedInputException,JackMappingExceptionandJacksonProcessingException) are logged and re-thrown wrapped intoUncheckedIOException.
-
Json-specific. Not supported in XML.
additionalProperties is a magic word that allows to serialize map with String keys as top-level properties.
Please use @JsonAnyGetter/@JsonAnySetter instead (if you can) for performance reasons.
- whole word
- case insensitive
- fields only
- has to be annotated with
@JsonProperty(auto-detection does not work) - any visibility
- can work with non-string keys if corresponding
KeySerializeris provided. - can work with non-string values
- serialization: map is serialized as top-level properties
- deserialization: any unknown properties are populated on the
additionalPropertiesas long as value type allows that (and throws otherwise)
additionalProperties are Azure SDK concept, @JsonAnyGetter/@JsonAnySetter is jackson annotations. Please use @JsonAnyGetter/@JsonAnySetter when possible. Usage of additionalProperties adds extra performance overhead (~10x) and is not recommended in perf-sensitive scenarios.
Flattening is Azure SDK concept that allows to write more compact models.
@JsonFlatten
class Model {
@JsonProperty("property.name")
private String name = "foo";
@JsonProperty("property.value")
private String value = "bar";
@JsonProperty("property\\.escaped")
private String escaped = "baz";
}translates into
{
"property" : {
"name" : "foo",
"value" : "bar"
},
"property.escaped" : "baz"
}class Model {
@JsonFlatten
@JsonProperty("property.name")
private String name = "foo";
@JsonFlatten
@JsonProperty("property.value")
private String value = "bar";
@JsonProperty("property.not.escaped")
private String notEscaped = "baz";
}translates into
{
"property" : {
"name" : "foo",
"value" : "bar"
},
"property.not.escaped" : "baz"
}- class or any field has to have
@JsonFlattenannotation, no auto-detection - properties (getters/setters) are not supported
- there is no limitation on depth
- class-level annotation:
- serialization: all fields that have unescaped
.are populated as nested fields - deserialization: all fields with unescaped
.in model definition are populated from nested nodes
- serialization: all fields that have unescaped
- field-level annotation:
- serialization: all annotated fields (with unescaped
.) are populated as nested fields- if there are multiple fields with the same root, one root node is created
- deserialization: all annotated fields (with unescaped
.) in model definition are populated from nested nodes
- serialization: all annotated fields (with unescaped
- escaping:
\.is used if class is annotated with@JsonFlattenand specific field does not need to be annotated. - Using
additionalProperties(or@JsonAnyGetter/@JsonAnySetter) along with@JsonFlattenon class or field levels is not supported (flattening is not possible).
-
Duration:- values: ISO 8601 String with a days component (e.g.
"P1DT10H17M36.789S"), controlled byDurationSerializer- jackson default
123456.789- number of seconds with nano precision
- jackson default
- keys (controlled by
JavaTimeModule)- serialization: not supported
- deserialization: Jackson default (Duration.parse(ISO 8601 String)
- values: ISO 8601 String with a days component (e.g.
-
Instant:- values: instant UTC ISO string (
"2021-07-06T19:47:12.728012100Z"), controlled byMapperBuilder.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)- jackson default: epoch time (number)
- keys (controlled by
JavaTimeModule)- serialization: not supported
- deserialization: Jackson default (Instant.Parse(ISO string))
- values: instant UTC ISO string (
-
OffsetDateTime- values: UTC date-time ISO string (
"2021-07-06T20:09:01.465447100Z"), controlled byDateTimeSerializer- jackson default: local date-time ISO string
"2021-07-06T13:11:08.3230678-07:00", controlled byMapperBuilder.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
- jackson default: local date-time ISO string
- keys (controlled by
JavaTimeModule)- serialization: not supported
- deserialization: Jackson default (OffsetDateTime.parse(ISO string))
- values: UTC date-time ISO string (
-
UnixTime- values: epoch seconds number (
1.625602953E9), controlled byUnixTimeSerializer- jackson default (
UnixTimenot supported) -{"dateTime":"2021-07-06T20:20:15.193979500Z"}and depends onJavaTimeModuleandWRITE_DATES_AS_TIMESTAMPS
- jackson default (
- keys: not supported
- values: epoch seconds number (
-
DateTimeRfc1123- values: RFC1123 string (
"Tue, 06 Jul 2021 20:31:19 GMT"), controlled byDateTimeRfc1123Serializer- jackson default - not supported -
{"dateTime":"2021-07-06T20:20:15.193979500Z"}and depends onJavaTimeModuleandWRITE_DATES_AS_TIMESTAMPS
- jackson default - not supported -
- keys: not supported
- values: RFC1123 string (
-
ZonedDateTime:- values: ISO local date-time string with time zone
"2021-07-06T14:08:08.0519546-07:00"(JavaTimeModuleandWRITE_DATES_AS_TIMESTAMPS) - keys (controlled by
JavaTimeModule)- serialization: not supported
- deserialization: Jackson default (
ZonedDateTime.parse(key, DateTimeFormatter.ISO_OFFSET_DATE_TIME))
- values: ISO local date-time string with time zone
Any few more with the same (de)serialization behavior as ZonedDateTime:
-
LocalDateTime-"2021-07-06T14:08:08.0389576" -
LocalDate-"2021-07-06" -
LocalTime-"14:08:08.0379605" -
MonthDay-"--07-06" -
OffsetTime-"14:08:08.050955100-07:00" -
Period-"P10D" -
Year-"2021" -
YearMonth-"2021-07" -
ZoneId-"-07:00" -
ZoneOffset-"-07:00"
- serialization: as a string
- deserialization: implicitly works through public constructor
Base64Url(String)
Serialized as Map<String, String>. If a header has multiple values, they are joined in a comma-separated string.
Using headers along with additionalProperties (or @JsonAnyGetter/@JsonAnySetter) is not supported.
Following models are (de)serialized using serializer provided with JsonSerializerProvider, defaulting to JacksonAdapter which no custom serializer is provided:
CloudEventBinaryDataRequestContent-
JsonPatchDocumentwith the exception that it does not allow using default implementation (TODO is is a bug?)
Consumers and Azure SDKs are encouraged to bring their own serializers to customize serialization for these models.
GeoObject and its friends are pure models and don't handle their own (de)serialization.
DynamicRequest accepts any ObjectSerializer implementation in constructor to serialize request body.
- If multiple (de)serializers are registered for the type, the last one to be set wins.
-
additionalPropertiesandJsonFlattenshould not be used in performance-sensitive cases. They have overhead on each (de)serialization call. Please consider adjusting models for data-plane scenarios. We assume they are used in management SDKs and management SDKs are less sensitive to perf.
- 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