DEV Community

Cover image for Revolutionize JSON Parsing in Java with Manifold
Shai Almog
Shai Almog

Posted on • Originally published at debugagent.com

Revolutionize JSON Parsing in Java with Manifold

Java developers have often envied JavaScript for its ease of parsing JSON. Although Java offers more robustness, it tends to involve more work and boilerplate code. Thanks to the Manifold project, Java now has the potential to outshine JavaScript in parsing and processing JSON files. Manifold is a revolutionary set of language extensions for Java that completely changes the way we handle JSON (and much more…).

Getting Started with Manifold

The code for this tutorial can be found on my GitHub page. Manifold is relatively young but already vast in its capabilities. You can learn more about the project on their website and Slack channel. To begin, you'll need to install the Manifold plugin, which is currently only available for JetBrains IDEs. The project supports LTS releases of Java, including the latest JDK 19.

We can install the plugin from IntelliJ/IDEAs settings UI by navigating to the marketplace and searching for Manifold. The plugin makes sure the IDE doesn’t collide with the work done by the Maven/Gradle plugin.

Image description

Manifold consists of multiple smaller projects, each offering a custom language extension. Today, we'll discuss one such extension, but there's much more to explore.

Setting Up a Maven Project

To demonstrate Manifold, we'll use a simple Maven project (it also works with Gradle). We first need to paste the current Manifold version from their website and add the necessary dependencies. The main dependency for JSON is the manifold-json-rt dependency. Other dependencies can be added for YAML, XML, and CSV support. We need to add this to the pom.xml file in the project.

I'm aware of the irony where the boilerplate reduction for JSON starts with a great deal of configuration in the Maven build script. But this is configuration, not "actual code" and it's mostly copy & paste. Notice that if you want to reduce this code the Gradle equivalent code is terse by comparison.

This line needs to go into the properties section:

<manifold.version>2023.1.5</manifold.version>
Enter fullscreen mode Exit fullscreen mode

The dependencies we use are these:

<dependencies>
   <dependency>
       <groupId>systems.manifold</groupId>
       <artifactId>manifold-json-rt</artifactId>
       <version>${manifold.version}</version>
   </dependency>
Enter fullscreen mode Exit fullscreen mode

The compilation plugin is the boilerplate that weaves Manifold into the bytecode and makes it seamless for us. It’s the last part of the pom setup:

<build>
   <plugins>
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.8.0</version>
           <configuration>
               <source>19</source>
               <target>19</target>
               <encoding>UTF-8</encoding>
               <compilerArgs>
                   <!-- Configure manifold plugin-->
                   <arg>-Xplugin:Manifold</arg>
               </compilerArgs>

               <!-- Add the processor path for the plugin -->
               <annotationProcessorPaths>
                   <path>
                       <groupId>systems.manifold</groupId>
                       <artifactId>manifold-json</artifactId>
                       <version>${manifold.version}</version>
                   </path>
               </annotationProcessorPaths>
           </configuration>
       </plugin>
   </plugins>
</build>
Enter fullscreen mode Exit fullscreen mode

With the setup complete, let's dive into the code.

Parsing JSON with Manifold

We place a sample JSON file in the project directory under the resources hierarchy. I placed this file under src/main/resources/com/debugagent/json/Test.json:

{
  "firstName": "Shai",
  "surname": "Almog",
  "website": "https://debugagent.com/",
  "active": true,
  "details":[
    {"key": "value"}
  ]
}
Enter fullscreen mode Exit fullscreen mode

In the main class, we refresh the Maven project, and you'll notice a new Test class appears. This class is dynamically created by Manifold based on the JSON file. If you change the JSON and refresh Maven, everything updates seamlessly. It’s important to understand that Manifold isn’t a code generator. It compiles the JSON we just wrote into bytecode.

The Test class comes with several built-in capabilities, such as a type-safe builder API that lets you construct JSON objects using builder methods. You can also generate nested objects and convert the JSON to a string by using the write() and toJson() methods.

It means we can now write:

Test test = Test.builder().withFirstName("Someone")
        .withSurname("Surname")
        .withActive(true)
        .withDetails(List.of(
                Test.details.detailsItem.builder().
                        withKey("Value 1").build()
        ))
        .build();
Enter fullscreen mode Exit fullscreen mode

Which will printout the following JSON:

{
  "firstName": "Someone",
  "surname": "Surname",
  "active": true,
  "details": [
    {
      "key": "Value 1"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

We can similarly read a JSON file using code such as this:

Test readObject = Test.load().fromJson("""
        {
          "firstName": "Someone",
          "surname": "Surname",
          "active": true,
          "details": [
            {
              "key": "Value 1"
            }
          ]
        }
        """);
Enter fullscreen mode Exit fullscreen mode

Note the use of Java 15 TextBlock syntax for writing a long string. The load() method returns an object that includes various APIs for reading the JSON. In this case, it is read from a String but there are APIs for reading it from a URL, file, etc.

Manifold supports various formats, including CSV, XML, and YAML, allowing you to generate and parse any of these formats without writing any boilerplate code or sacrificing type safety. In order to add that support we will need to add additional dependencies to the pom.xml file:

   <dependency>
       <groupId>systems.manifold</groupId>
       <artifactId>manifold-csv-rt</artifactId>
       <version>${manifold.version}</version>
   </dependency>
   <dependency>
       <groupId>systems.manifold</groupId>
       <artifactId>manifold-xml-rt</artifactId>
       <version>${manifold.version}</version>
   </dependency>
   <dependency>
       <groupId>systems.manifold</groupId>
       <artifactId>manifold-yaml-rt</artifactId>
       <version>${manifold.version}</version>
   </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

With these additional dependencies, this code will print out the same data as the JSON file... With test.write().toCsv() the output would be:

"firstName","surname","active","details"
"Someone","Surname","true","[manifold.json.rt.api.DataBindings@71070b9c]"
Enter fullscreen mode Exit fullscreen mode

Notice that the Comma Separated Values (CSV) output doesn’t include hierarchy information. That’s a limitation of the CSV format and not the fault of Manifold.

With test.write().toXml() the output is familiar and surprisingly concise:

<root_object firstName="Someone" surname="Surname" active="true">
  <details key="Value 1"/>
</root_object>
Enter fullscreen mode Exit fullscreen mode

With test.write().toYaml() we again get a familiar printout:

firstName: Someone
surname: Surname
active: true
details:
- key: Value 1
Enter fullscreen mode Exit fullscreen mode

Working with JSON Schema

Manifold also works seamlessly with JSON schema, allowing you to enforce strict rules and constraints. This is particularly useful when working with dates and enums. Manifold seamlessly creates/updates byte code that adheres to the schema, making it much easier to work with complex JSON data.

This schema is copied and pasted from the Manifold github project:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/schemas/User.json",
  "type": "object",
  "definitions": {
    "Gender": {
      "type": "string",
      "enum": ["male", "female"]
    }
  },
  "properties": {
    "name": {
      "type": "string",
      "description": "User's full name.",
      "maxLength": 80
    },
    "email": {
      "description": "User's email.",
      "type": "string",
      "format": "email"
    },
    "date_of_birth": {
      "type": "string",
      "description": "Date of uses birth in the one and only date standard: ISO 8601.",
      "format": "date"
    },
    "gender": {
      "$ref" : "#/definitions/Gender"
    }
  },
  "required": ["name", "email"]
}
Enter fullscreen mode Exit fullscreen mode

It’s a relatively simple schema but I’d like to turn your attention to several things here. It defines name and email as required. This is why when we try to create a User object using a builder in Manifold, the build() method requires both parameters:

User.builder("Name", "email@domain.com")
Enter fullscreen mode Exit fullscreen mode

That is just the start... The schema includes a date. Dates are a painful prospect in JSON, the standardization is poor and fraught with issues. The schema also includes a gender field which is effectively an enum. This is all converted to type-safe semantics using common Java classes such as LocalDate:

User u = User.builder("Name", "email@domain.com")
       .withDate_of_birth(LocalDate.of(1999, 10, 11))
       .withGender(User.Gender.male)
       .build();
Enter fullscreen mode Exit fullscreen mode

That can be made even shorter with static imports but the gist of the idea is clear. JSON is effectively native to Java in Manifold.

The Tip of The Iceberg

Manifold is a powerful and exciting project. It revolutionizes JSON parsing in Java but that’s just one tiny portion of what it can do!

We've only scratched the surface of its capabilities in this post. In the next article, we'll dive deeper into Manifold and explore some additional unexpected features.

Please share your experience and thoughts about Manifold in the comments section. If you have any questions, don't hesitate to ask.

Top comments (4)

Collapse
 
samiekblad profile image
Sami Ekblad

Builder pattern and plugin look interesting. How would you compare this to Jackson ObjectMapper?

Collapse
 
codenameone profile image
Shai Almog

The goals of manifold are very different. Unlike Jakson you won't need to maintain an object map. Just provide a reference JSON or Schema. This means you have a single source of truth that is very consistent with the API you're working with.

It's a radically different approach that aims for seamless integration. I've yet to work on this with a "real world" project but from playing with it the syntax is fantastic.

I am concerned about edge cases. E.g. weird/inconsistent JSON syntax, etc. This sucks with all JSON/Java frameworks though.

Collapse
 
samiekblad profile image
Sami Ekblad

Thanks! The "single source of truth" is really good goal. And the syntax is indeed nice. I think keeping the same option open as Lombok vs Delombok: I can see that the explicit JSON Java classes might still make sense in some apps. Or apps in maintenance mode (when code is read/debugged not written anymore)?

Thread Thread
 
codenameone profile image
Shai Almog

I'm not sure if it's practical in the case of Manifold as the project is so much more vast when compared to Lombok. But it would be an interesting exercise.