How to map JSONPath expressions in Java

JSONPath is a powerful tool for querying JSON data, similar to XPath for XML. Combining JSONPath with Jackson, a popular Java library for JSON processing, allows for advanced mapping of JSON structures to Java objects. This tutorial demonstrates how to map JSONPath expressions using Jackson and Jayway JsonPath.

Prerequisites

When working with JSON in Java, it’s often necessary to extract specific data from complex structures. While Jackson provides robust mechanisms for mapping JSON fields to Java objects, there are scenarios where JSONPath, a query language for JSON, can complement Jackson’s functionality. This tutorial illustrates how to leverage JSONPath expressions for advanced data mapping in Java applications.

To run this tutorial examples, you will need the following tools:

  • Java Development Kit (JDK) installed
  • Jackson library (com.fasterxml.jackson.core)
  • Jayway JsonPath library (com.jayway.jsonpath)

Then, add the following dependencies to your Maven project:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.0</version>
    </dependency>
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <version>2.7.0</version>
    </dependency>
</dependencies>

A practical JSON Example

Consider the following JSON structure:

{
  "person": {
    "name": "John Doe",
    "phoneNumbers": [
      {
        "type": "home",
        "number": "123-456-7890"
      },
      {
        "type": "work",
        "number": "098-765-4321"
      }
    ]
  }
}

In this article, we will show how to map the type attribute of the first phone number ($.phoneNumbers[:1].type) to a field in a Java class.

Step 1: Create the Java Class

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jayway.jsonpath.JsonPath;
import com.fasterxml.jackson.databind.JsonNode;

public class Person {
    private String name;

    @JsonIgnore // Exclude this field from automatic mapping
    private String firstPhoneType;

    @JsonCreator
    public Person(@JsonProperty("name") String name, @JsonProperty("phoneNumbers") JsonNode phoneNumbers) {
        this.name = name;

        // Use JSONPath to extract the type of the first phone number
        String json = phoneNumbers.toString();
        this.firstPhoneType = JsonPath.parse(json).read("$[0].type");
    }

    public String getName() {
        return name;
    }

    public String getFirstPhoneType() {
        return firstPhoneType;
    }

    public void setFirstPhoneType(String firstPhoneType) {
        this.firstPhoneType = firstPhoneType;
    }
}

Step 2: Write the Test Code

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonPathMappingExample {
    public static void main(String[] args) throws Exception {
        String json = """
            {
              "person": {
                "name": "John Doe",
                "phoneNumbers": [
                  { "type": "home", "number": "123-456-7890" },
                  { "type": "work", "number": "098-765-4321" }
                ]
              }
            }
        """;

        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.readTree(json).path("person");

        // Map the JSON to the Person object
        Person person = mapper.treeToValue(rootNode, Person.class);
        System.out.println("Name: " + person.getName());
        System.out.println("First Phone Type: " + person.getFirstPhoneType());
    }
}

Step 3: Run the Code

Compile and run the program. The output should be:

Name: John Doe
First Phone Type: home

JSON Annotations

Within this article, we have used the following annotations:

  1. JsonCreator: Customizes object creation during deserialization.
  2. JsonProperty: Maps JSON fields to constructor parameters.
  3. JsonIgnore: Prevents automatic serialization/deserialization of specific fields.
  4. JsonPath: Reads and applies JSONPath queries on the phoneNumbers JSON node to extract the desired value.

Besides, if you need a reusable approach for mapping JSONPath expressions, you can create a custom annotation and a deserialization handler. This can be useful for projects requiring extensive JSONPath mappings.

Conclusion

By combining Jackson and Jayway JsonPath, you can map JSONPath expressions to Java object fields, enabling fine-grained control over JSON parsing and data extraction.

Was this article helpful? We need your support to keep MasterTheBoss alive!