Core JavaJava

Parse JSON with Manifold: JSON Schema-Aware Parsing

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:

manifold parsing json example output

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 new Person object. It requires the required properties (name and age) as arguments, just like the create() method.
  • withEmail(): This is an optional step where you can fluently set additional properties, such as the email in this case.
  • build(): This method completes the object creation and returns the fully constructed Person 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 a Person instance, telling the program that a Person 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 the Person 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 is readOnly and should not be part of the JSON used to create a new user.
  • The password field is writeOnly 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.

Download
You can download the full source code of this example here: manifold parsing json

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button