Package com.azure.json
The Azure JSON library provides interfaces for stream-style JSON reading and writing. Stream-style reading and writing has the type itself define how to read JSON to create an instance of itself and how it writes out to JSON. Azure JSON also allows for external implementations for JSON reading and writing by offering a service provider interface to load implementations from the classpath. However, if one is not found, the Azure JSON library provides a default implementation.
Getting Started
JsonSerializable is the base of Azure JSON: it's the interface that types implement to
provide stream-style JSON reading and writing functionality. The interface has a single implementable method
toJson(JsonWriter) that defines how the
object is written as JSON, to the JsonWriter, and a static method
fromJson(JsonReader) that defines how to
read an instance of the object from JSON, being read from the JsonReader. The default
implementation of fromJson(JsonReader)
throws an UnsupportedOperationException if the static method isn't hidden (a static method with the
same definition) by the type implementing JsonSerializable. Given that the type itself manages
JSON serialization the type can be fluent, immutable, or a mix of fluent and immutable, it doesn't matter as all
logic is self-encapsulated.
Sample: All JsonSerializable fields are optional
/**
* Implementation of JsonSerializable where all properties are fluently set.
*/
public class ComputerMemory implements JsonSerializable<ComputerMemory> {
private long memoryInBytes;
private double clockSpeedInHertz;
private String manufacturer;
private boolean errorCorrecting;
/**
* Sets the memory capacity, in bytes, of the computer memory.
*
* @param memoryInBytes The memory capacity in bytes.
* @return The update ComputerMemory
*/
public ComputerMemory setMemoryInBytes(long memoryInBytes) {
this.memoryInBytes = memoryInBytes;
return this;
}
/**
* Sets the clock speed, in hertz, of the computer memory.
*
* @param clockSpeedInHertz The clock speed in hertz.
* @return The update ComputerMemory
*/
public ComputerMemory setClockSpeedInHertz(double clockSpeedInHertz) {
this.clockSpeedInHertz = clockSpeedInHertz;
return this;
}
/**
* Sets the manufacturer of the computer memory.
*
* @param manufacturer The manufacturer.
* @return The update ComputerMemory
*/
public ComputerMemory setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
return this;
}
/**
* Sets whether the computer memory is error correcting.
*
* @param errorCorrecting Whether the computer memory is error correcting.
* @return The update ComputerMemory
*/
public ComputerMemory setErrorCorrecting(boolean errorCorrecting) {
this.errorCorrecting = errorCorrecting;
return this;
}
@Override
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
return jsonWriter.writeStartObject()
.writeLongField("memoryInBytes", memoryInBytes)
.writeDoubleField("clockSpeedInHertz", clockSpeedInHertz)
// Writing fields with nullable types won't write the field if the value is null. If a nullable field needs
// to always be written use 'writeNullableField(String, Object, WriteValueCallback<JsonWriter, Object>)'.
// This will write 'fieldName: null' if the value is null.
.writeStringField("manufacturer", manufacturer)
.writeBooleanField("errorCorrecting", errorCorrecting)
.writeEndObject();
}
/**
* Reads an instance of ComputerMemory from the JsonReader.
*
* @param jsonReader The JsonReader being read.
* @return An instance of ComputerMemory if the JsonReader was pointing to an instance of it, or null if it was
* pointing to JSON null.
* @throws IOException If an error occurs while reading the ComputerMemory.
*/
public static ComputerMemory fromJson(JsonReader jsonReader) throws IOException {
// 'readObject' will initialize reading if the JsonReader hasn't begun JSON reading and validate that the
// current state of reading is a JSON start object. If the state isn't JSON start object an exception will be
// thrown.
return jsonReader.readObject(reader -> {
ComputerMemory deserializedValue = new ComputerMemory();
while (reader.nextToken() != JsonToken.END_OBJECT) {
String fieldName = reader.getFieldName();
reader.nextToken();
// In this case field names are case-sensitive but this could be replaced with 'equalsIgnoreCase' to
// make them case-insensitive.
if ("memoryInBytes".equals(fieldName)) {
deserializedValue.setMemoryInBytes(reader.getLong());
} else if ("clockSpeedInHertz".equals(fieldName)) {
deserializedValue.setClockSpeedInHertz(reader.getDouble());
} else if ("manufacturer".equals(fieldName)) {
deserializedValue.setManufacturer(reader.getString());
} else if ("errorCorrecting".equals(fieldName)) {
deserializedValue.setErrorCorrecting(reader.getBoolean());
} else {
// Fallthrough case of an unknown property. In this instance the value is skipped, if it's a JSON
// array or object the reader will progress until it terminated. This could also throw an exception
// if unknown properties should cause that or be read into an additional properties Map for further
// usage.
reader.skipChildren();
}
}
return deserializedValue;
});
}
}
Sample: All JsonSerializable fields are required
/**
* Implementation of JsonSerializable where all properties are set in the constructor.
*/
public class ComputerProcessor implements JsonSerializable<ComputerProcessor> {
private final int cores;
private final int threads;
private final String manufacturer;
private final double clockSpeedInHertz;
private final OffsetDateTime releaseDate;
/**
* Creates an instance of ComputerProcessor.
*
* @param cores The number of physical cores.
* @param threads The number of virtual threads.
* @param manufacturer The manufacturer of the processor.
* @param clockSpeedInHertz The clock speed, in hertz, of the processor.
* @param releaseDate The release date of the processor, if unreleased this is null.
*/
public ComputerProcessor(int cores, int threads, String manufacturer, double clockSpeedInHertz,
OffsetDateTime releaseDate) {
// This constructor could be made package-private or private as 'fromJson' has access to internal APIs.
this.cores = cores;
this.threads = threads;
this.manufacturer = manufacturer;
this.clockSpeedInHertz = clockSpeedInHertz;
this.releaseDate = releaseDate;
}
@Override
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
return jsonWriter.writeStartObject()
.writeIntField("cores", cores)
.writeIntField("threads", threads)
.writeStringField("manufacturer", manufacturer)
.writeDoubleField("clockSpeedInHertz", clockSpeedInHertz)
// 'writeNullableField' will always write a field, even if the value is null.
.writeNullableField("releaseDate", releaseDate, (writer, value) -> writer.writeString(value.toString()))
.writeEndObject()
// In this case 'toJson' eagerly flushes the JsonWriter.
// Flushing too often may result in performance penalties.
.flush();
}
/**
* Reads an instance of ComputerProcessor from the JsonReader.
*
* @param jsonReader The JsonReader being read.
* @return An instance of ComputerProcessor if the JsonReader was pointing to an instance of it, or null if it was
* pointing to JSON null.
* @throws IOException If an error occurs while reading the ComputerProcessor.
* @throws IllegalStateException If any of the required properties to create ComputerProcessor aren't found.
*/
public static ComputerProcessor fromJson(JsonReader jsonReader) throws IOException {
return jsonReader.readObject(reader -> {
// Local variables to keep track of what values have been found.
// Some properties have a corresponding 'boolean found<Name>' to track if a JSON property with that name
// was found. If the value wasn't found an exception will be thrown at the end of reading the object.
int cores = 0;
boolean foundCores = false;
int threads = 0;
boolean foundThreads = false;
String manufacturer = null;
boolean foundManufacturer = false;
double clockSpeedInHertz = 0.0D;
boolean foundClockSpeedInHertz = false;
OffsetDateTime releaseDate = null;
while (reader.nextToken() != JsonToken.END_OBJECT) {
String fieldName = reader.getFieldName();
reader.nextToken();
// Example of case-insensitive names.
if ("cores".equalsIgnoreCase(fieldName)) {
cores = reader.getInt();
foundCores = true;
} else if ("threads".equalsIgnoreCase(fieldName)) {
threads = reader.getInt();
foundThreads = true;
} else if ("manufacturer".equalsIgnoreCase(fieldName)) {
manufacturer = reader.getString();
foundManufacturer = true;
} else if ("clockSpeedInHertz".equalsIgnoreCase(fieldName)) {
clockSpeedInHertz = reader.getDouble();
foundClockSpeedInHertz = true;
} else if ("releaseDate".equalsIgnoreCase(fieldName)) {
// For nullable primitives 'getNullable' must be used as it will return null if the current token
// is JSON null or pass the reader to the non-null callback method for reading, in this case for
// OffsetDateTime it uses 'getString' to call 'OffsetDateTime.parse'.
releaseDate = reader.getNullable(nonNullReader -> OffsetDateTime.parse(nonNullReader.getString()));
} else {
reader.skipChildren();
}
}
// Check that all required fields were found.
if (foundCores && foundThreads && foundManufacturer && foundClockSpeedInHertz) {
return new ComputerProcessor(cores, threads, manufacturer, clockSpeedInHertz, releaseDate);
}
// If required fields were missing throw an exception.
throw new IOException("Missing one, or more, required fields. Required fields are 'cores', 'threads', "
+ "'manufacturer', and 'clockSpeedInHertz'.");
});
}
}
Sample: JsonSerializable contains required and optional fields
/**
* Implementation of JsonSerializable where some properties are set in the constructor and some properties are set using
* fluent methods.
*/
public class VmStatistics implements JsonSerializable<VmStatistics> {
private final String vmSize;
private final ComputerProcessor processor;
private final ComputerMemory memory;
private final boolean acceleratedNetwork;
private Map<String, Object> additionalProperties;
/**
* Creates an instance VmStatistics.
*
* @param vmSize The size, or name, of the VM type.
* @param processor The processor of the VM.
* @param memory The memory of the VM.
* @param acceleratedNetwork Whether the VM has accelerated networking.
*/
public VmStatistics(String vmSize, ComputerProcessor processor, ComputerMemory memory, boolean acceleratedNetwork) {
this.vmSize = vmSize;
this.processor = processor;
this.memory = memory;
this.acceleratedNetwork = acceleratedNetwork;
}
/**
* Sets additional properties about the VM.
*
* @param additionalProperties Additional properties of the VM.
* @return The update VmStatistics
*/
public VmStatistics setAdditionalProperties(Map<String, Object> additionalProperties) {
this.additionalProperties = additionalProperties;
return this;
}
@Override
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
jsonWriter.writeStartObject()
.writeStringField("VMSize", vmSize)
.writeJsonField("Processor", processor)
.writeJsonField("Memory", memory)
.writeBooleanField("AcceleratedNetwork", acceleratedNetwork);
// Include additional properties in JSON serialization.
if (additionalProperties != null) {
for (Map.Entry<String, Object> additionalProperty : additionalProperties.entrySet()) {
jsonWriter.writeUntypedField(additionalProperty.getKey(), additionalProperty.getValue());
}
}
return jsonWriter.writeEndObject();
}
/**
* Reads an instance of VmStatistics from the JsonReader.
*
* @param jsonReader The JsonReader being read.
* @return An instance of VmStatistics if the JsonReader was pointing to an instance of it, or null if it was
* pointing to JSON null.
* @throws IOException If an error occurs while reading the VmStatistics.
* @throws IllegalStateException If any of the required properties to create VmStatistics aren't found.
*/
public static VmStatistics fromJson(JsonReader jsonReader) throws IOException {
return jsonReader.readObject(reader -> {
String vmSize = null;
boolean foundVmSize = false;
ComputerProcessor processor = null;
boolean foundProcessor = false;
ComputerMemory memory = null;
boolean foundMemory = false;
boolean acceleratedNetwork = false;
boolean foundAcceleratedNetwork = false;
Map<String, Object> additionalProperties = null;
while (reader.nextToken() != JsonToken.END_OBJECT) {
String fieldName = reader.getFieldName();
reader.nextToken();
// Example of case-insensitive names and where serialization named don't match field names.
if ("VMSize".equalsIgnoreCase(fieldName)) {
vmSize = reader.getString();
foundVmSize = true;
} else if ("Processor".equalsIgnoreCase(fieldName)) {
// Pass the JsonReader to another JsonSerializable to read the inner object.
processor = ComputerProcessor.fromJson(reader);
foundProcessor = true;
} else if ("Memory".equalsIgnoreCase(fieldName)) {
memory = ComputerMemory.fromJson(reader);
foundMemory = true;
} else if ("AcceleratedNetwork".equalsIgnoreCase(fieldName)) {
acceleratedNetwork = reader.getBoolean();
foundAcceleratedNetwork = true;
} else {
// Fallthrough case but the JSON property is maintained.
if (additionalProperties == null) {
// Maintain ordering of additional properties using a LinkedHashMap.
additionalProperties = new LinkedHashMap<>();
}
// Additional properties are unknown types, use 'readUntyped'.
additionalProperties.put(fieldName, reader.readUntyped());
}
}
// Check that all required fields were found.
if (foundVmSize && foundProcessor && foundMemory && foundAcceleratedNetwork) {
return new VmStatistics(vmSize, processor, memory, acceleratedNetwork)
.setAdditionalProperties(additionalProperties);
}
// If required fields were missing throw an exception.
throw new IOException("Missing one, or more, required fields. Required fields are 'VMSize', 'Processor',"
+ "'Memory', and 'AcceleratedNetwork'.");
});
}
}
Reading and Writing JSON
JsonReader contains APIs and logic for parsing JSON. The type is abstract and consists of
both abstract methods for an implementation to implement as well as final method for commonly shared logic that
builds on the abstract methods. Similarly, JsonWriter contains APIs and logic for writing
JSON, and as with JsonReader, it contains both abstract methods for implementations to
implement and final methods for commonly shared logic that builds on the abstract methods. Both types implement
Closeable and should be used in try-with-resources blocks to ensure any resources created by
the implementations are cleaned up once JSON reading or writing is complete. Both types are used by the
JsonProvider service provider interface which is used to create instances of
JsonReader and JsonWriter implementations.
JsonProviders is a utility class that handles finding JsonProvider
implementations on the classpath and should be the default way to create instances of
JsonReader and JsonWriter. As mentioned earlier, the Azure JSON
package provides a default implementation allowing for the library to be used stand-alone.
JsonReader can be created from byte[], String,
InputStream, and Reader sources, JsonWriter can be created
from OutputStream and Writer sources. No matter the source the functionality will be
the same, the options exist to provide the best convenience and performance by reducing type translations.
Sample: Reading a JSON byte[]
// Sample uses String.getBytes as a convenience to show the JSON string in a human-readable form.
byte[] json = ("{\"memoryInBytes\":10000000000,\"clockSpeedInHertz\":4800000000,"
+ "\"manufacturer\":\"Memory Corp\",\"errorCorrecting\":true}").getBytes(StandardCharsets.UTF_8);
try (JsonReader jsonReader = JsonProviders.createReader(json)) {
return ComputerMemory.fromJson(jsonReader);
}
Sample: Reading a JSON String
String json = "{\"cores\":16,\"threads\":32,\"manufacturer\":\"Processor Corp\","
+ "\"clockSpeedInHertz\":5000000000,\"releaseDate\":null}";
try (JsonReader jsonReader = JsonProviders.createReader(json)) {
return ComputerProcessor.fromJson(jsonReader);
}
Sample: Reading a JSON InputStream
// Sample uses String.getBytes as a convenience to show the JSON string in a human-readable form.
InputStream json = new ByteArrayInputStream(("{\"VMSize\":\"large\",\"Processor\":{\"cores\":8,"
+ "\"threads\"16\",\"manufacturer\":\"Processor Corp\",\"clockSpeedInHertz\":4000000000,"
+ "\"releaseDate\":\"2023-01-01\"},\"Memory\":{\"memoryInBytes\":10000000000,"
+ "\"clockSpeedInHertz\":4800000000,\"manufacturer\":\"Memory Corp\",\"errorCorrecting\":true},"
+ "\"AcceleratedNetwork\":true,\"CloudProvider\":\"Azure\",\"Available\":true}")
.getBytes(StandardCharsets.UTF_8));
try (JsonReader jsonReader = JsonProviders.createReader(json)) {
return VmStatistics.fromJson(jsonReader);
}
Sample: Reading a JSON Reader
Reader json = new StringReader("{\"VMSize\":\"large\",\"Processor\":{\"cores\":8,\"threads\"16\","
+ "\"manufacturer\":\"Processor Corp\",\"clockSpeedInHertz\":4000000000,\"releaseDate\":\"2023-01-01\"},"
+ "\"Memory\":{\"memoryInBytes\":10000000000,\"clockSpeedInHertz\":4800000000,"
+ "\"manufacturer\":\"Memory Corp\",\"errorCorrecting\":true},\"AcceleratedNetwork\":true,"
+ "\"CloudProvider\":\"Azure\",\"Available\":true}");
try (JsonReader jsonReader = JsonProviders.createReader(json)) {
return VmStatistics.fromJson(jsonReader);
}
Sample: Writing to a JSON OutputStream
Map<String, Object> additionalVmProperties = new LinkedHashMap<>();
additionalVmProperties.put("CloudProvider", "Azure");
additionalVmProperties.put("Available", true);
VmStatistics vmStatistics = new VmStatistics("large",
new ComputerProcessor(8, 16, "Processor Corp", 4000000000D, OffsetDateTime.parse("2023-01-01")),
new ComputerMemory()
.setMemoryInBytes(10000000000L)
.setClockSpeedInHertz(4800000000D)
.setManufacturer("Memory Corp")
.setErrorCorrecting(true),
true)
.setAdditionalProperties(additionalVmProperties);
ByteArrayOutputStream json = new ByteArrayOutputStream();
try (JsonWriter jsonWriter = JsonProviders.createWriter(json)) {
// JsonWriter automatically flushes on close.
vmStatistics.toJson(jsonWriter);
}
// {"VMSize":"large","Processor":{"cores":8,"threads":16,"manufacturer":"Processor Corp",
// "clockSpeedInHertz":4000000000.0,"releaseDate":"2023-01-01"},"Memory":{"memoryInBytes":10000000000,
// "clockSpeedInHertz":4800000000.0,"manufacturer":"Memory Corp","errorCorrecting":true},
// "AcceleratedNetwork":true,"CloudProvider":"Azure","Available":true}
System.out.println(json);
Sample: Writing to a JSON Writer
Map<String, Object> additionalVmProperties = new LinkedHashMap<>();
additionalVmProperties.put("CloudProvider", "Azure");
additionalVmProperties.put("Available", true);
VmStatistics vmStatistics = new VmStatistics("large",
new ComputerProcessor(8, 16, "Processor Corp", 4000000000D, OffsetDateTime.parse("2023-01-01")),
new ComputerMemory()
.setMemoryInBytes(10000000000L)
.setClockSpeedInHertz(4800000000D)
.setManufacturer("Memory Corp")
.setErrorCorrecting(true),
true)
.setAdditionalProperties(additionalVmProperties);
Writer json = new StringWriter();
try (JsonWriter jsonWriter = JsonProviders.createWriter(json)) {
// JsonWriter automatically flushes on close.
vmStatistics.toJson(jsonWriter);
}
// {"VMSize":"large","Processor":{"cores":8,"threads":16,"manufacturer":"Processor Corp",
// "clockSpeedInHertz":4000000000.0,"releaseDate":"2023-01-01"},"Memory":{"memoryInBytes":10000000000,
// "clockSpeedInHertz":4800000000.0,"manufacturer":"Memory Corp","errorCorrecting":true},
// "AcceleratedNetwork":true,"CloudProvider":"Azure","Available":true}
System.out.println(json);
- See Also:
-
ClassDescriptionContains configuration options for creating a
JsonReaderorJsonWriter.An interface to be implemented by any azure-json plugin that wishes to provide an alternateJsonReaderorJsonWriterimplementation.Utility class forJsonProviderthat will use the implementation ofJsonProviderfound on the classpath to create instances ofJsonReaderorJsonWriter.Reads a JSON value as a stream of tokens.JsonSerializable<T extends JsonSerializable<T>>Indicates that the implementing class can be serialized to and deserialized from JSON.Token types used when reading JSON content.Context of JSON handling.Writes a JSON value as a stream of tokens.Writing context of the JSON stream.ReadValueCallback<T,R> A callback used when reading a JSON value, such asJsonReader.readArray(ReadValueCallback).WriteValueCallback<T,U> A callback used when writing a JSON value, such asJsonWriter.writeArray(Object[], WriteValueCallback).