Manifold is a set of Java plugins that adds useful language features. This article explores Manifold JSON, a tool that makes working with JSON easier and safer. Manifold JSON uses a JSON Schema to automatically create types that match the structure of the JSON data. This helps ensure the data is correct and simplifies handling JSON.
1. What is Manifold?
Manifold is a plugin for the Java compiler that adds useful features to Java projects, making development more productive. It enables type-safe integration of data, metadata, or domain-specific languages (DSLs) directly into Java at compile-time. With Manifold, we can easily work with formats like JSON, YAML, XML, CSV, SQL, GraphQL, and JavaScript, using the right dependencies.
Manifold also enhances the Java language with features like extension methods, true delegation, properties, tuple expressions, operator overloading, and more. These features are available as separate dependencies, which can be added to existing projects.
2. Setting Up Manifold in a Project
Before parsing JSON, configure Manifold by adding these dependencies to your Maven pom.xml
.
<dependencies> <!-- Manifold JSON runtime --> <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-json-rt</artifactId> <version>2024.1.33</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>17</source> <target>17</target> <compilerArgs> <arg>-Xplugin:Manifold</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>systems.manifold</groupId> <artifactId>manifold-json</artifactId> <version>2024.1.33</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
The dependencies section includes the manifold-json-rt
runtime library, which is necessary for running Manifold JSON at runtime. In the build
section, The compilerArgs
block includes the -Xplugin:Manifold
argument, which activates the Manifold plugin during compilation. The annotationProcessorPaths
section specifies the manifold-json
dependency, which enables Manifold to process JSON Schema files and generate corresponding Java types during compilation.
3. Using Manifold to Parse JSON
To effectively use Manifold for parsing JSON, First, we will create a JSON Schema file that represents the structure of the JSON data. This file should be placed in the resources directory of our Maven project, typically located at src/main/resources
. The content of the Person.json
file should resemble the following:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://jcg.com/manifold/Person.json", "type": "object", "properties": { "name": { "type": "string" }, "age": { "type": "integer" }, "email": { "type": "string", "format": "email" } }, "required": ["name", "age"] }
This schema defines a Person
object with three properties: name
, age
, and email
. The name
and age
fields are required, while the email
field is optional.
Once we have the dependencies and JSON file in place, the Maven build process will automatically trigger Manifold’s compiler plugin to process the JSON schema. During compilation, Manifold generates Java classes based on the JSON schema without us needing to explicitly call any generation commands.
We can compile your project using:
mvn clean compile
3.1 Creating and Using Manifold-Generated JSON Classes
In this section, we will see how to create and use an instance of the Person
class that was automatically generated by Manifold from our JSON Schema. The following example shows how to instantiate the class, and set additional properties in a Java application.
import com.jcg.manifold.Person; public class CreateJsonExample { public static void main(String[] args) { // Create an instance of the generated Person class using the create() method Person person = Person.create("Alice Johnson", 56); person.setEmail("alice.johnson@jcg.com"); // Access the values System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); System.out.println("Email: " + person.getEmail()); } }
In this code, the Person.create()
method is called to create a new Person
instance. The create()
method takes parameters that match the required properties set in the JSON Schema. If there are no required properties (e.g., "required": ["name", "age"]
) in the JSON schema file, then the create()
method does not need any parameters. In this case, the Person.create()
method requires two parameters that match the required properties: the name and age. The setEmail()
method is then used to set the email property, which was not included when the object was initially created.
Once the class runs successfully, we should see the following output in the terminal or IDE console:
Alternatively, the builder()
method can be used to create an instance in a more flexible way, allowing properties to be set step by step. Here is an example of how to use the builder()
method:
public class BuildJsonExample { public static void main(String[] args) { // Create an instance of the generated Person class using the builder() method Person person = Person.builder("Lee Clarke", 24) .withEmail("mr.lee@jcg.com").build(); } }
Person.builder()
: This method is used to start creating a newPerson
object. It requires the required properties (name and age) as arguments, just like thecreate()
method.withEmail()
: This is an optional step where you can fluently set additional properties, such as theemail
in this case.build()
: This method completes the object creation and returns the fully constructedPerson
instance.
The builder()
method provides a more flexible approach, especially when dealing with optional fields, allowing us to set them in a readable and intuitive way.
3.2 Loading JSON
Manifold simplifies loading instances from various sources, such as a string, file, or network request, using the load()
method. You can load a Person
instance from formats like JSON, XML, CSV, or YAML strings. Let’s take a look at how to load a Person
instance, first from a JSON string and then from a file.
3.2.1 Loading a Person
instance from a JSON String
public class LoadJsonExample { public static void main(String[] args) { Person p = Person.load().fromJson(""" { "name": "Alice Felix", "age": 33, "email": "user@jcg.com" } """); System.out.println("" + p.getName()); } }
This block of code shows how to load a Person
instance from a JSON string using the load().fromJson()
method.
Person.load()
method initiates the loading process for aPerson
instance, telling the program that aPerson
object will be created from a data source.fromJson()
method specifies that the data source is in JSON format. It parses the JSON content to populate thePerson
instance.- After the JSON string is parsed, a
Person
object (p
) is created with the given values: (name: “Alice Felix”, age: 33, email: “user@jcg.com”)
3.2.2 Loading a Person
instance from a JSON File
import java.io.InputStream; import java.io.InputStreamReader; public class LoadJsonFromFile { public static void main(String[] args) { InputStream is = LoadJsonFromFile.class.getResourceAsStream("/com/jcg/manifold/People.json"); InputStreamReader reader = new InputStreamReader(is); Person person = Person.load().fromJsonReader(reader); System.out.println(person.getName()); } }
This code shows how to load a Person
instance from a JSON file. It starts by retrieving the JSON file (People.json
) as an input stream using getResourceAsStream()
, then wraps the input stream with an InputStreamReader
to read the file’s content. Finally, the Person.load().fromJsonReader(reader)
method parses the JSON content from the reader and creates a Person
object based on the data in the file.
3.3 Writing JSON
Manifold makes writing JSON easy by allowing the conversion of Java objects into JSON strings using the write()
method.
public class WriteJsonExample { public static void main(String[] args) { // Create a Person object Person person = Person.builder("Lee Clarke", 24) .withEmail("mr.lee@jcg.com").build(); // Convert Person object to JSON String jsonOutput = person.write().toJson(); System.out.println("Generated JSON: " + jsonOutput); } }
This code demonstrates how to create a Person
object and convert it into a JSON string using the write().toJson()
method. First, a Person
instance is created using the builder()
method with the name “Lee Clarke” and age 24, followed by setting the email. The write().toJson()
method is then used to convert the Person
object into a JSON-formatted string, which is printed to the console.
Output:
Generated JSON: { "name": "Lee Clarke", "age": 24, "email": "mr.lee@jcg.com" }
3.3.1 Exporting to Alternate Formats
Manifold provides the ability to easily convert Java objects into various formats using the write()
method. The following methods are available for generating different formatted strings:
toYaml()
: This method generates a YAML-formatted string, which is known for its readability and is commonly used for configuration files.toXml()
: This method creates an XML-formatted string, useful for applications that require data to be structured in XML format.toCsv()
: This method outputs a CSV-formatted string, allowing for easy representation of tabular data.
To enable these functionalities, we need to update the pom.xml
file by adding the following dependencies:
<dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-csv-rt</artifactId> <version>2024.1.33</version> </dependency> <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-xml-rt</artifactId> <version>2024.1.33</version> </dependency> <dependency> <groupId>systems.manifold</groupId> <artifactId>manifold-yaml-rt</artifactId> <version>2024.1.33</version> </dependency>
These dependencies allow the necessary runtime support for converting our Java objects into CSV, XML, and YAML formats, ensuring that the export functionalities work.
public class AlternateFormatExamples { public static void main(String[] args) { Person person = Person.builder("Lee Clarke", 24) .withEmail("mr.lee@jcg.com").build(); // Convert Person object to different formats String yamlOutput = person.write().toYaml(); System.out.println("Generated YAML: " + yamlOutput); String xmlOutput = person.write().toXml(); System.out.println("Generated XML: " + xmlOutput); String csvOutput = person.write().toCsv(); System.out.println("Generated CSV: " + csvOutput); } }
3.4 Handling readOnly
and writeOnly
Properties
Sometimes, certain properties in a JSON schema are marked as readOnly
or writeOnly
. For example, a password
field might be writeOnly
to ensure it’s never returned in a JSON response, while an id
field might be readOnly
to prevent updates. Manifold can handle these properties according to the schema rules.
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://jcg.com/manifold/User.json", "type": "object", "properties": { "id": { "type": "integer", "readOnly": true }, "name": { "type": "string" }, "password": { "type": "string", "writeOnly": true } }, "required": ["name", "password"] }
- The
id
field isreadOnly
and should not be part of the JSON used to create a new user. - The
password
field iswriteOnly
and will not appear when fetching a user.
4. Conclusion
In this article, we explored how Manifold simplifies working with JSON in Java by providing powerful tools for JSON Schema-aware parsing and generation. We covered how to configure Manifold in a Maven project, create and load JSON instances, and convert Java objects into various formats such as JSON, YAML, XML, and CSV. With Manifold, we can work more efficiently by integrating JSON directly into Java code, ensuring type safety and minimizing manual parsing or external code generation.
5. Download the Source Code
This article covered how to use Manifold for parsing JSON.
You can download the full source code of this example here: manifold parsing json