Package com.azure.json


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: