Monday, June 17, 2024

Filter a Map by Keys and Values using Java Stream

Filter a Map by Keys and Values using Java Stream

Discover how to filter a Map by keys, values, or both using Java 8’s Stream API with the filter() and collect() methods. These straightforward code snippets demonstrate how to create generic functions for filtering any Map based on its keys or values. Let us delve into understanding how to filter a map by keys and values.

1. Introduction


In Java, a Map is a part of the Java Collections Framework and is used to store key-value pairs. Each key in a map is unique, and each key maps to exactly one value. Maps provide a way to look up values based on their corresponding keys, making data retrieval efficient.

Common implementations of the Map interface include HashMap, TreeMap, and LinkedHashMap. These implementations differ in terms of ordering, performance, and underlying data structures. For instance, HashMap offers average constant-time performance for most operations like get and put, while TreeMap maintains a sorted order of keys.

Maps are versatile and widely used in Java applications to manage and manipulate collections of data where relationships between keys and values are important.

1.1 Filter a Map by Keys


To filter a map by keys, we can use the Stream API to process the entries and collect them back into a map. Here is an example:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
 
public class FilterMapByKey {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("cherry", 30);
        map.put("date", 40);
 
        // Filter the map to keep only keys starting with 'a'
        Map filteredMap = map.entrySet()
            .stream()
            .filter(entry -> entry.getKey().startsWith("a"))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 
        System.out.println(filteredMap);
    }
}

Here’s a code breakdown:

  • map.entrySet().stream() – Creates a stream from the map’s entry set.
  • filter(entry -> entry.getKey().startsWith("a")) – Filters the entries where the key starts with ‘a’.
  • collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) – Collects the filtered entries back into a map.

The code output is:

{apple=10}

1.2 Filter a Map by Values


Filtering a map by values follows a similar approach. Here is an example:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
 
public class FilterMapByValue {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("cherry", 30);
        map.put("date", 40);
 
        // Filter the map to keep only values greater than 20
        Map filteredMap = map.entrySet()
            .stream()
            .filter(entry -> entry.getValue() > 20)
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 
        System.out.println(filteredMap);
    }
}

Here’s a code breakdown:

  • map.entrySet().stream() – Creates a stream from the map’s entry set.
  • filter(entry -> entry.getValue() > 20) – Filters the entries where the value is greater than 20.
  • collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) – Collects the filtered entries back into a map.

The code output is:

{date=40, cherry=30}

1.3 Filter a Map by Keys and Values, Both


Sometimes, you may need to filter a map by both keys and values. Here is how you can do that:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
 
public class FilterMapByKeyAndValue {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("cherry", 30);
        map.put("date", 40);
 
        // Filter the map to keep only keys starting with 'c' and values greater than 20
        Map filteredMap = map.entrySet()
            .stream()
            .filter(entry -> entry.getKey().startsWith("c") && entry.getValue() > 20)
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 
        System.out.println(filteredMap);
    }
}

Here’s a code breakdown:

  • map.entrySet().stream() – Creates a stream from the map’s entry set.
  • filter(entry -> entry.getKey().startsWith("c") && entry.getValue() > 20) – Filters the entries where the key starts with ‘c’ and the value is greater than 20.
  • collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) – Collects the filtered entries back into a map.

The code output is:

{cherry=30}

Using the Stream API in Java 8 and later versions, filtering maps by keys, values, or both becomes a clean and efficient process. The examples provided demonstrate how to perform these operations effectively.

2. Conclusion


In conclusion, filtering maps by keys, values, or both in Java can be efficiently accomplished using the Stream API introduced in Java 8. By leveraging the power of streams, developers can create concise and readable code to handle these common tasks. Whether you need to filter entries based on specific key patterns, value ranges, or a combination of both, the examples provided demonstrate the flexibility and ease with which these operations can be performed. This approach not only enhances code readability but also ensures that operations on collections are both functional and expressive.

Source: javacodegeeks.com

Friday, June 14, 2024

How to Use Pair With Java PriorityQueue

How to Use Pair With Java PriorityQueue

Java’s PriorityQueue is a data structure that allows us to store and retrieve elements in a specific order. This article explores how to use pairs with PriorityQueue and demonstrates how to use Comparator interface to control the sorting order.

1. What is a PriorityQueue?


A PriorityQueue is a queue data structure where elements are ordered based on their priority rather than their insertion order. This data structure is part of the Java Collections Framework and is typically used when processing elements in a specific order, defined by their natural ordering or a custom comparator.

1.1 Ordering

One of the key characteristics of PriorityQueue is its ordering mechanism. Elements are ordered based on their priority. The default priority is determined by the natural ordering of elements (meaning the elements must implement the Comparable interface), or we can define a custom Comparator to specify the order.

1.2 What is a Pair?

The Pair class in Java was a convenient utility class found in JavaFX, which allowed developers to store a pair of values, essentially providing a simple way to return two related objects from a method. A Pair object holds two values, referred to as key and value, and provide methods to access these values. Here is a simple usage example:

import javafx.util.Pair;
 
Pair<Integer, String> pair = new Pair<>(1, "one");
System.out.println("Key: " + pair.getKey());
System.out.println("Value: " + pair.getValue());

The Pair class (javafx.util.Pair), along with other parts of JavaFX, was decoupled from the JDK in Java 11. The removal of JavaFX from the JDK meant that the Pair class is no longer a built-in part of the standard Java library from Java 11 onwards.

Developers needing Pair functionality now often resort to either custom implementations or third-party libraries such as Apache Commons Lang (org.apache.commons.lang3.tuple.Pair).

2. Using PriorityQueue with a Custom Class


To use PriorityQueue with a Custom class, we will create a class that implements the Comparable interface. Let’s consider an example using a custom class named Book, which includes a title and a publication year.

Book.java

public class Book implements Comparable<Book>{
 
    String title;
    int year;
 
    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }
 
    public String getTitle() {
        return title;
    }
 
    public int getYear() {
        return year;
    }
     
    @Override
    public int compareTo(Book other) {
        // Compare Books based on their year
        return Integer.compare(this.year, other.year);
    }
 
    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}

In this example, the Book class implements the Comparable interface.

Next, we create a PriorityQueue of Book objects, add some books to the priority queue and process elements from the PriorityQueue.

PriorityQueueExample1.java

public class PriorityQueueExample1 {
 
    public static void main(String[] args) {
 
        // Create a PriorityQueue and Add Books to the Queue
        PriorityQueue<Book> bookQueue = new PriorityQueue<>();
        bookQueue.add(new Book("To Kill a Mockingbird", 1960));
        bookQueue.add(new Book("1984", 1949));
        bookQueue.add(new Book("The Age of Reason", 1794));
        bookQueue.add(new Book("The Great Gatsby", 1925));
 
        // Process Books
        while (!bookQueue.isEmpty()) {
            Book book = bookQueue.poll();
            System.out.println(book);
        }
    }
}

This code will create a PriorityQueue of Book objects, sorted by year in ascending order. The output is:

The Age of Reason (1794)
The Great Gatsby (1925)
1984 (1949)
To Kill a Mockingbird (1960)

3. Using Comparators with PriorityQueue


To customize the sorting order in the PriorityQueue, we can use a Comparator.

3.1 Using Comparator.comparing()

Comparator.comparing() method from the Comparator class enables us to create comparators in a straightforward and readable manner. By leveraging Comparator.comparing(), we can specify custom sorting logic for a PriorityQueue like this:

ProrityQueueExample2.java

public class ProrityQueueExample2 {
 
    public static void main(String[] args) {
 
        PriorityQueue<Book> bookQueue = new PriorityQueue<>(Comparator.comparingInt(Book::getYear));
 
        bookQueue.add(new Book("To Kill a Mockingbird", 1960));
        bookQueue.add(new Book("1984", 1949));
        bookQueue.add(new Book("The Age of Reason", 1794));
        bookQueue.add(new Book("The Great Gatsby", 1925));
 
        while (!bookQueue.isEmpty()) {
            Book book = bookQueue.poll();
            System.out.println(book);
        }
 
    }
}

Here, the Books are sorted based on the year element in ascending order. Note that the Book class does not need to implement the Comparable interface for this example.

3.2 Using Lambda Expression

We can also utilize lambda expressions as follows:

public class ProrityQueueExample2 {
 
    public static void main(String[] args) {
 
       // Using Lambda expressions
        PriorityQueue<Book> bookQueue = new PriorityQueue<>((Book b1, Book b2) -> Integer.compare(b1.getYear(), b2.getYear()));
         
        bookQueue.add(new Book("To Kill a Mockingbird", 1960));
        bookQueue.add(new Book("1984", 1949));
        bookQueue.add(new Book("The Age of Reason", 1794));
        bookQueue.add(new Book("The Great Gatsby", 1925));
 
        while (!bookQueue.isEmpty()) {
            Book book = bookQueue.poll();
            System.out.println(book);
        }
 
    }
}

3.3 Changing the Sorting Order

To change the sorting order, simply modify the Comparator. For example, the code fragment below shows how to sort Books in descending order based on the year value:

PriorityQueue<Book> bookQueue = new PriorityQueue<>((Book b1, Book b2) -> Integer.compare(b2.getYear(), b1.getYear()));

The output becomes:

To Kill a Mockingbird (1960)
1984 (1949)
The Great Gatsby (1925)
The Age of Reason (1794)

Output demonstrating the use of a comparator to sort a Java PriorityQueue of pairs in descending order

4. Using Apache Commons Pair


The Apache Commons Lang library provides a Pair class which is a simple container to store a pair of objects. Using Apache Commons Pair with Java’s PriorityQueue can enhance the handling of paired data with specific priorities. Here’s an example of combining Apache Commons Pair with PriorityQueue:

ApachePairPriorityQueueExample.java

import java.util.Comparator;
import org.apache.commons.lang3.tuple.Pair;
import java.util.PriorityQueue;
 
public class ApachePairPriorityQueueExample {
 
    public static void main(String[] args) {
 
        // Example usage of the Pair class with PriorityQueue
        // PriorityQueue is initialized with the comparator
        PriorityQueue<Pair<String, Integer>> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(Pair::getValue));
 
        // Adding Pairs to the priority queue
        priorityQueue.add(Pair.of("Finish Article", 3));
        priorityQueue.add(Pair.of("Buy Milk", 1));
        priorityQueue.add(Pair.of("Call Mom", 2));
 
        // Polling elements from the priority queue
        while (!priorityQueue.isEmpty()) {
            Pair<String, Integer> pair = priorityQueue.poll();
            System.out.println("Priority " + pair.getValue() + " : " + pair.getKey());
        }
    }
}

5. Conclusion

In this article, we explored how to use Java’s PriorityQueue with custom classes and the Apache Commons Pair class. We started with an overview of the PriorityQueue and delved into examples demonstrating how to create and manipulate a PriorityQueue with custom pairs, emphasizing how to use Comparator to control the sorting order. We also covered an example using a custom Book class, showcasing how PriorityQueue can be adapted to manage more complex objects. Finally, we demonstrated how to utilize Apache Commons’ Pair class for similar purposes.

Wednesday, June 12, 2024

Int to short Conversion in Java

Int to short Conversion in Java

When working with Java, we frequently face situations that require converting data types to meet specific needs. A common example is converting an int to a short. Let’s delve into understanding the Java Int to Short conversion process.

1. Convert from int to short in Java


In Java, converting an int to a short can be done in two ways.

◉ Casting int to short
◉ Using the Integer.shortValue() Method

1.1 Casting int to short


Casting is a straightforward way to convert an int to a short in Java. This involves explicitly telling the compiler to convert the int to a short. Since short is a smaller data type (16-bit) compared to int (32-bit), this can result in data loss if the int value exceeds the range of short (-32,768 to 32,767).

public class IntToShortCasting {
    public static void main(String[] args) {
        int intValue = 32000;
        short shortValue = (short) intValue;
        System.out.println("The int value: " + intValue);
        System.out.println("The short value: " + shortValue);
    }
}

Here’s a code breakdown:

  • intValue: This is the integer value that we want to convert.
  • shortValue = (short) intValue: The int value is cast to a short using the casting syntax.
  • System.out.println(): These lines print out the original int value and the converted short value.

The code output is:

The int value: 32000
 
The short value: 32000

If the int value is out of the short range, the result will be unexpected due to overflow:

public class IntToShortCastingOverflow {
    public static void main(String[] args) {
        int intValue = 33000;
        short shortValue = (short) intValue;
        System.out.println("The int value: " + intValue);
        System.out.println("The short value: " + shortValue);
    }
}

The code output is:

The int value: 33000
 
The short value: -32536

Here, the short value wraps around due to exceeding its maximum limit.

1.2. Using the Integer.shortValue()method


Another way to convert an int to a short is by using the Integer.shortValue() method. This method is part of the Integer class, which wraps a value of the primitive type int in an object.

public class IntToShortMethod {
    public static void main(String[] args) {
        Integer intValue = 32000;
        short shortValue = intValue.shortValue();
        System.out.println("The int value: " + intValue);
        System.out.println("The short value: " + shortValue);
    }
}

Here’s a code breakdown:

  • Integer intValue: Here, the int value is wrapped in an Integer object.
  • short shortValue = intValue.shortValue(): The shortValue() method is called on the Integer object to get its value as a short.
  • System.out.println(): These lines print out the original Integer value and the converted short value.

The code output is:

The int value: 32000
 
The short value: 32000

Similar to casting, if the int value is out of the short range, the result will also be unexpected:

public class IntToShortMethodOverflow {
    public static void main(String[] args) {
        Integer intValue = 33000;
        short shortValue = intValue.shortValue();
        System.out.println("The int value: " + intValue);
        System.out.println("The short value: " + shortValue);
    }
}

The code output is:

The int value: 33000
 
The short value: -32536

Again, the short value wraps around due to exceeding its maximum limit.

1.3 Potential Pitfalls


When converting from int to short in Java, there are several potential pitfalls to be aware of:

  • Data Loss: Since short has a smaller range than int, values that are too large or too small will not be accurately represented, leading to data loss.
  • Overflow: If the int value exceeds the range of short (-32,768 to 32,767), it will wrap around, resulting in unexpected and incorrect values.
  • Performance Considerations: Although casting and using the shortValue() method are both efficient, unnecessary conversions can lead to code that is harder to read and maintain.
  • Semantic Clarity: Converting types can sometimes obscure the original intent of the code, especially if the conversion is not well-documented or understood by other developers working on the same codebase.

Source: javacodegeeks.com

Monday, June 10, 2024

How to Traverse All Files from a Folder in Java

How to Traverse All Files from a Folder in Java

Traversing all files in a folder is a common task in Java, whether you need to read files, filter them based on certain criteria, or process them in some way. Java provides several ways to achieve this, from the traditional File class to the more modern java.nio.file package. This article will guide you through different methods for traversing files in a folder using Java.

1. Folder Structure Example


Let’s consider the folder /Users/omozegieaziegbe/development/oraclejavacertified/ containing the following files and folder structure for demonstration:

exampleFolder
    file4.doc
    file5.pdf
    subFolder1
        file.log
        file.txt
    subFolder2
        file3.txt
        subSubFolder2
            file5.txt

Directory structure used for example on traversing all files from a folder in Java

2. Using File.listFiles()


The File class is part of the java.io package and has been available since Java 1.0. It provides basic methods to list files and directories. To traverse all files and folders, including those within subdirectories, we need to recursively process each directory.

2.1 Example: Traverse All Files and Folders in a Directory Using File.listFiles()

FileTraversal.java

public class FileTraversal {
 
    public static void main(String[] args) {
 
        // Specify the directory path
        String directoryPath = "/Users/omozegieaziegbe/development/oraclejavacertified/";
 
        // Using File class (pre-Java 7)
        File directory = new File(directoryPath);
        traverseFiles(directory);
    }
 
    public static void traverseFiles(File folder) {
        if (folder.isDirectory()) {
            File[] files = folder.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        traverseFiles(file); // Recursive call for subdirectories
                    } else {
                        System.out.println("File: " + file.getAbsolutePath());
                    }
                }
            }
        }
    }
}

2.2 Explanation

  • Create a File Object: new File("path/to/your/folder") creates a File object representing the folder to traverse.
  • Check if It’s a Directory: Use isDirectory() to confirm the object is a directory.
  • List Files and Directories: listFiles() returns an array of File objects representing the files and directories in the folder.
  • Recursive Traversal: For each File object, if it’s a directory, print its path and recursively call traverseFiles(file). If it’s a file, print its path.

Output:

Running this code with the provided folder structure will produce the following output:

File: /Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/subFolder2/file3.txt
File: /Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/subFolder2/subSubFolder2/file5.txt
File: /Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/file5.pdf
File: /Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/file4.doc
File: /Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/subFolder1/file.txt
File: /Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/subFolder1/file.log

3. Traverse All Files and Folders Using Files.walk()


The Files.walk() method from the java.nio.file package offers a convenient way to traverse all files and directories within a folder and its subfolders. It returns a Stream of Path objects that can be filtered and processed using stream operations.

3.1 Example: Using Files.walk() to Traverse All Files

NIOFileTraversal.java

public class NIOFileTraversal {
 
    public static void main(String[] args) {
 
        Path folderPath = Paths.get("/Users/omozegieaziegbe/development/oraclejavacertified/"); // Adjust this path to match your folder structure
        try (Stream<Path> paths = Files.walk(folderPath)) {
            paths.filter(Files::isRegularFile)
                    .forEach(System.out::println); // Process each file
        } catch (IOException e) {
        }
    }
}

How to Traverse All Files from a Folder in Java
The program above uses Files.walk() method to generate a Stream of Path objects representing all files and directories within the specified folder and its subfolders. The filter(Files::isRegularFile) method filters out directories, leaving only regular files in the stream. The forEach(System.out::println) method processes each file by printing its path to the console.

3.2 Advantages of Using Files.walk()

  • Concise and Readable: The use of streams makes the code concise and easy to read.
  • Flexible: You can easily filter, map, and process the paths using various stream operations.
  • Handles Large Directories: The lazy evaluation of streams allows efficient handling of large directories without loading all paths into memory.

4. Find a File from a Folder and Its Subfolders


Finding a specific file in a directory and its subdirectories can be efficiently achieved using the Files.walk() API. This involves filtering the stream based on the file name or other criteria.

4.1 Example: Find a Specific File

FindFile.java

public class FindFile {
 
    public static void main(String[] args) {
         
        Path folderPath = Paths.get("/Users/omozegieaziegbe/development/oraclejavacertified/");
        String fileNameToFind = "file.log";
         
        try (Stream<Path> paths = Files.walk(folderPath)) {
            Optional<Path> foundFile = paths.filter(Files::isRegularFile)
                                            .filter(path -> path.getFileName().toString().equals(fileNameToFind))
                                            .findFirst();
 
            foundFile.ifPresent(System.out::println); // Process the found file
        } catch (IOException e) {
        }
    }
}

4.2 Explanation

  • Create a Path Object: Paths.get("path/to/your/folder") creates a Path object for the root folder.
  • Use Files.walk(): This method generates a stream of paths for the entire directory tree.
  • Filter and Find: The stream is filtered to include only regular files, and further filtered to match the desired file name. findFirst() is used to return an Optional<Path> of the first matching file.
  • Process the Found File: If the file is found, its path is printed or we can replace this with any other processing logic.

Running the code above will give the following output:

/Users/omozegieaziegbe/development/oraclejavacertified/exampleFolder/subFolder1/file.log

Source: javacodegeeks.com

Friday, June 7, 2024

Gson Support for Java 8 Date-Time Types

Gson Support for Java 8 Date-Time Types

Java 8 introduced a robust date and time API with classes such as LocalDate, LocalDateTime, and ZonedDateTime. These classes provide an improved way to handle dates and times compared to the legacy java.util.Date and java.util.Calendar classes. However, by default, Gson does not support these new date-time types. This lack of support can lead to issues when trying to serialize and deserialize these types to and from JSON. This article will demonstrate how to create these custom serializers and deserializers for LocalDate, LocalDateTime, and ZonedDateTime.

1. Set Up Gson


To use Gson, first, ensure the library is included in the project. If using Maven, add the following dependency to the pom.xml:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.11.0</version>
</dependency>

2. Example Problem


Consider a simple Java class containing a LocalDate field:

Person.java

public class Person {
    private String name;
    private LocalDate birthDate;
 
    public Person(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }
 
    // Getters and setters omitted for brevity
}

Now, let’s try to serialize an instance of this Person class using default Gson:

DefaultGsonExample.java

public class DefaultGsonExample {
 
    public static void main(String[] args) {
         
        Person person = new Person("John Doe", LocalDate.of(2022, 10, 1));
        Gson gson = new Gson();
        String json = gson.toJson(person);
        System.out.println("Serialized JSON: " + json);
    }
}

Output is:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.google.gson.internal.reflect.ReflectionHelper (file:/Users/omozegieaziegbe/.m2/repository/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar) to field java.time.LocalDate.year
WARNING: Please consider reporting this to the maintainers of com.google.gson.internal.reflect.ReflectionHelper
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Serialized JSON: {"name":"John Doe","birthDate":{"year":2022,"month":10,"day":1}}

As seen, the birthDate field is not properly serialized. Now let’s try to deserialize a JSON string back to a Person object:

DefaultGsonExample.java

public class DefaultGsonExample {
 
    public static void main(String[] args) {
        String json = "{\"name\":\"John Doe\",\"birthDate\":\"1990-01-01\"}";
        Gson gson = new Gson();
        Person person = gson.fromJson(json, Person.class);
        System.out.println("Deserialized Person: " + person.getBirthDate());
    }
}

Output is:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 33 path $.birthDate
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read (ReflectiveTypeAdapterFactory.java:397)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.readIntoField (ReflectiveTypeAdapterFactory.java:212)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter.readField (ReflectiveTypeAdapterFactory.java:433)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read (ReflectiveTypeAdapterFactory.java:393)
    at com.google.gson.Gson.fromJson (Gson.java:1227)
    at com.google.gson.Gson.fromJson (Gson.java:1137)
    at com.google.gson.Gson.fromJson (Gson.java:1047)
    at com.google.gson.Gson.fromJson (Gson.java:982)
    at com.jcg.defaultjsonexample.DefaultGsonExample.main (DefaultGsonExample.java:18)
    at org.codehaus.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:279)
    at java.lang.Thread.run (Thread.java:834)

The deserialization fails with a JsonSyntaxException, indicating that Gson expected a JSON object but encountered a string instead. This occurs because Gson does not know how to handle the LocalDate type, resulting in a mismatch between the expected and actual JSON structures.

3. Solution: Custom Serializers and Deserializers


To resolve this issue, we need to create custom serializers and deserializers for Java 8 date-time types and register them with Gson. These custom adapters convert LocalDateTime instances to JSON strings and vice versa. This is crucial because LocalDateTime is not natively supported by Gson, and attempting to serialize or deserialize LocalDateTime objects without custom adapters will result in errors or incorrect data representation.

3.1 LocalDate Serializer and Deserializer

We will create a class for handling LocalDate type in Gson.

LocalDateAdapter.java

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
 
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
 
public class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
 
    private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
 
    @Override
    public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src.format(formatter));
    }
 
    @Override
    public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return LocalDate.parse(json.getAsString(), formatter);
    }
}

3.1.1 Register LocalDateAdapter with Gson

With the custom serializer and deserializer LocalDateAdapter ready, we need to register it with a Gson instance.

CustomDateTimeExample.java

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.time.LocalDate;
 
public class CustomDateTimeExample {
 
    public static void main(String[] args) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
                .create();
 
        Person person = new Person("John Doe", LocalDate.of(2022, 10, 1));    
         
        // Serialize
        String json = gson.toJson(person);
        System.out.println("Serialized Person with LocalDate: " + json);
         
        //Deserialize
        Person deserializedPerson = gson.fromJson(json, Person.class);
        System.out.println("Deserialized Person with LocalDate: " + deserializedPerson);
    }
 
}

The program output is:

Serialized Person with LocalDate: {"name":"John Doe","birthDate":"2022-10-01"}
Deserialized Person with LocalDate: Person(name=John Doe, birthDate=2022-10-01)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

Output from using custom serializer and deserializer for gson support with Java 8 local time

3.2 LocalDateTime Serializer and Deserializer

Create a LocalDateTimeAdapter class to implement both JsonSerializer<LocalDateTime> and JsonDeserializer<LocalDateTime>. I used the DateTimeFormatter.ISO_LOCAL_DATE_TIME to ensure that the date-time is formatted according to the ISO-8601 standard.

LocalDateTimeAdapter.java

import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonParseException;
 
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
 
public class LocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
 
    @Override
    public JsonElement serialize(LocalDateTime src, Type typeOfSrc, com.google.gson.JsonSerializationContext context) {
        return new JsonPrimitive(src.format(formatter));
    }
 
    @Override
    public LocalDateTime deserialize(JsonElement json, Type typeOfT, com.google.gson.JsonDeserializationContext context) throws JsonParseException {
        return LocalDateTime.parse(json.getAsString(), formatter);
    }
}

Explanation

◉ Serialization: The serialize method takes a LocalDateTime object and converts it to a JSON primitive string using the ISO-8601 format. This ensures that the LocalDateTime is represented as a standard string in JSON.
◉ Deserialization: The deserialize method takes a JSON element (expected to be a string) and converts it back to a LocalDateTime object using the same ISO-8601 format. This ensures that the string is correctly parsed back into a LocalDateTime instance.

3.2.1 Register LocalDateTimeAdapter with Gson

To handle LocalDateTime objects correctly, we need to register our custom LocalDateTimeAdapter with Gson. Here is how to register the LocalDateTimeAdapter:

CustomDateTimeExample.java

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.time.LocalDateTime;
 
public class CustomDateTimeExample {
 
    public static void main(String[] args) {
       Gson gson = new GsonBuilder()
                .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
                .create();
 
 
        Person person = new Person("John Doe", LocalDateTime.now());    
         
        // Serialize
        String json = gson.toJson(person);
        System.out.println("Serialized Person with LocalDateTime: " + json);
         
        //Deserialize
        Person deserializedPerson = gson.fromJson(json, Person.class);
        System.out.println("Deserialized Person with LocalDateTime: " + deserializedPerson);
    }
 
}

Output is:

Serialized Person with LocalDateTime: {"name":"John Doe","birthDate":"2024-06-05T17:32:36.982656"}
Deserialized Person with LocalDateTime: Person{name=John Doe, birthDate=2024-06-05T17:32:36.982656}

3.3 ZonedDateTime Serializer and Deserializer

Let’s create a ZonedDateTimeAdapter class that implements both JsonSerializer<ZonedDateTime> and JsonDeserializer<ZonedDateTime>. I used the DateTimeFormatter.ISO_ZONED_DATE_TIME to ensure that the date-time, including the time zone information, is formatted according to the ISO-8601 standard.

ZonedDateTimeAdapter.java

import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonParseException;
 
import java.lang.reflect.Type;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
 
 
public class ZonedDateTimeAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
 
    @Override
    public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, com.google.gson.JsonSerializationContext context) {
        return new JsonPrimitive(src.format(formatter));
    }
 
    @Override
    public ZonedDateTime deserialize(JsonElement json, Type typeOfT, com.google.gson.JsonDeserializationContext context) throws JsonParseException {
        return ZonedDateTime.parse(json.getAsString(), formatter);
    }
}

3.3.1 Register ZonedDateTimeAdapter with Gson

Similarly, to handle ZonedDateTime objects, we need to register our custom ZonedDateTimeAdapter with Gson. This ensures that Gson knows how to correctly serialize and deserialize ZonedDateTime instances, preserving both the date-time and the time zone information.

Here’s how to register the ZonedDateTimeAdapter:

CustomDateTimeExample.java

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.time.ZonedDateTime;
 
public class CustomDateTimeExample {
 
    public static void main(String[] args) {
       Gson gson = new GsonBuilder()
                .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter())
                .create();
 
 
        Person person = new Person("John Doe", ZonedDateTime.now());    
         
        // Serialize
        String json = gson.toJson(person);
        System.out.println("Serialized Person with ZonedDateTime: " + json);
         
        //Deserialize
        Person deserializedPerson = gson.fromJson(json, Person.class);
        System.out.println("Deserialized Person with ZonedDateTime: " + deserializedPerson);
    }
}

In the code above, the registerTypeAdapter method is used to register our custom-type adapter (ZonedDateTimeAdapter) for the ZonedDateTime class.

Output is:

Serialized Person with ZonedDateTime: {"name":"John Doe","birthDate":"2024-06-05T17:36:30.947555+01:00[Africa/Lagos]"}
Deserialized Person with ZonedDateTime: Person{name=John Doe, birthDate=2024-06-05T17:36:30.947555+01:00[Africa/Lagos]}

Source: javacodegeeks.com

Wednesday, June 5, 2024

Map Subset of JSON via Jackson

Map Subset of JSON via Jackson

1. Introduction


JavaScript Object Notation (JSON) is a text-based data format and widely used in the APIs for exchanging data between a client and server. The Jackson library from FasterXML is the most popular library for serializing Java objects to JSON and vice-versa. Sometimes, the JSON data returned from the server is a complex data structure as it considers all clients’ requirements. However, a client may only need a subset of the JSON data. This is similar to creating a database view based on tables. In this example, I will demonstrate how to map a subset of JSON using Jackson libraries.

2. Project Setup


In this step, I will set up a maven project with Jackson libraries to read three JSON files and map a subset of JSON into a Java POJO.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.zheng.demo</groupId>
    <artifactId>jackson-subset</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
 
        <!--
        https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.1</version>
        </dependency>
 
        <!--
        https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.17.1</version>
        </dependency>
 
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Here is the screenshot of the project setup.

Map Subset of JSON via Jackson

Figure 1. Project Setup

Note: There are two sets of Customer and Order POJOs which map to the desired subset of the JSON fields. The ones under org.zheng.demo.data package do not have the root element configured but the ones under org.zheng.demo.type package have the root element configured.

3. JSON Files


In this step, I will create three JSON files that will be used at step 4 and 5.

  • customer.json – this JSON contains a customer and its two orders.
  • customerWithWrapRoot.json – this JSON contains the default root from Jackson – the simple class name.
  • customWithJsonType.json – this JSON contains a customized root from Jackson with @JsonTypeName annotation.

3.1 Customer JSON File

The customer.json file contains a customer and its two orders.

customer.json

{
    "custTag" : "major",
    "email" : "test@test.com",
    "id" : 30,
    "name" : "Zheng",
    "orders" : [ {
      "productName" : "test",
      "quantity" : 10,
      "ignoreOrder":"should not be found in POJO"
    } ,
     {
      "productName" : "test",
      "quantity" : 1,
      "ignoreOrder":"should not be found in POJO"
    } ],
    "phone":"636-123-2345",
    "balance":1234.56,
    "rewardPoint":100
  }

Note: for this example, the client only wants to map the highlighted fields: custTag, email, id, name, and order‘s productName and quantity.

3.2 Custom with Wrapp_Root JSON File

The customerWithWrapRoot.json contains the root element with its simple class name – Customer.

customerWithWrapRoot.json

{
    "Customer": {
        "custTag": "major",
        "email": "test@test.com",
        "id": 30,
        "name": "Zheng",
        "unknowCustom": "not showing in this project",
        "orders": [
            {
                "productName": "test",
                "unknowOrder": "not showing in this project",
                "quantity": 10
            },
            {
                "productName": "test",
                "unknowOrder": "not showing in this project",
                "quantity": 10
            }
        ],
        "phone":"636-123-2345",
        "balance":1234.56,
        "rewardPoint":100
    }
}

◉ Line 2: this JSON contains the default wrap_root: Customer.

3.3 Customer with Customized Root JSON File

The customerWithJsonType.json contains the customized root element name – customer.

customerWithJsonType.json

{
    "customer": {
        "custTag": "major",
        "email": "test@test.com",
        "id": 30,
        "name": "Zheng",
        "orders": [
            {
                "order": {
                    "productName": "test",
                    "unknowOrder": "not showing in this project",
                    "quantity": 10
                }
            },
            {
                "order": {
                    "productName": "test",
                    "unknowOrder": "not showing in this project",
                    "quantity": 10
                }
            }
        ],
        "phone":"636-123-2345",
        "balance":1234.56,
        "rewardPoint":100
    }
}

◉ Line 2: this JSON contains the “customer” root node.
◉ Line 9, 16: the orders node contains the “order” root node.

4. Map Subset to Java Object


In this step, I will create two Java data objects annotated with @JsonIgnoreProperties(ignoreUnknown =true) so they can be mapped to a subset of the JSON file created at step 3.1.

4.1 Customer Object

In this step, I will create a simple Customer class which annotates with @JsonIgnoreProperties(ignoreUnknown = true). This class contains five fields from a total of eight fields from Customer.json file.

Customer.java

package org.zheng.demo.data;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
@JsonIgnoreProperties(ignoreUnknown = true)
public class Customer implements Serializable {
 
    private static final long serialVersionUID = 5963349342478710542L;
 
    private String custTag;
 
    private String email;
 
    private int id;
 
    private String name;
 
    private List<Order> orders;
 
    public Customer() {
        super();
    }
 
    public Customer(String name, int id) {
        super();
        this.id = id;
        this.name = name;
    }
 
    public void addOrder(Order order) {
        if (this.orders == null) {
            this.orders = new ArrayList<>();
        }
        this.orders.add(order);
    }
 
    public String getCustTag() {
        return custTag;
    }
 
    public String getEmail() {
        return email;
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
    public List<Order> getOrders() {
        return orders;
    }
 
    public void setCustTag(String custTag) {
        this.custTag = custTag;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setOrders(List<Order> orders) {
        this.orders = orders;
    }
 
}

◉ Note: only custTag, email, id, name, and orders are mapped.

4.2 Order Object

In this step, I will create a simple Order.java which maps two fields from a total of three fields from Customer.json‘s orders node.

Order.java

package org.zheng.demo.data;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
@JsonIgnoreProperties(ignoreUnknown = true)
public class Order {
 
    private String productName;
 
    private int quantity;
 
    public Order() {
        super();
    }
 
    public Order(int quantity, String name) {
        super();
        this.quantity = quantity;
        this.productName = name;
    }
 
    public String getProductName() {
        return productName;
    }
 
    public int getQuantity() {
        return quantity;
    }
 
    public void setProductName(String name) {
        this.productName = name;
    }
 
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
 
}

Note: only productName and quantity are mapped.

4.3 Customer & Order with Root

In this step, I will create another set of Customer and Order classes under the org.zheng.demo.type package.

The Customer class has a similar data structure as defined at step 4.1 except with the @JsonTypeName annotation and has an extra field: rewardPoint.

Customer.java

package org.zheng.demo.type;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
 
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeName("customer") 
@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME)
public class Customer implements Serializable {
 
    private static final long serialVersionUID = 5963349342478710542L;
 
    private String custTag;
 
    private String email;
 
    private int id;
 
    private String name;
 
    private List<Order> orders;
     
    private int rewardPoint;
 
    public Customer() {
        super();
    }
 
    public Customer(String name, int id) {
        super();
        this.id = id;
        this.name = name;
    }
 
    public void addOrder(Order order) {
        if (this.orders == null) {
            this.orders = new ArrayList<>();
        }
        this.orders.add(order);
    }
 
    public String getCustTag() {
        return custTag;
    }
 
    public String getEmail() {
        return email;
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
    public List<Order> getOrders() {
        return orders;
    }
 
    public void setCustTag(String custTag) {
        this.custTag = custTag;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setOrders(List<Order> orders) {
        this.orders = orders;
    }
 
    public int getRewardPoint() {
        return rewardPoint;
    }
 
    public void setRewardPoint(int rewardPoint) {
        this.rewardPoint = rewardPoint;
    }
 
}

The Order.java class is same as the class defined at step 4.2 except the @JsonTypeName annotation.

Order.java

package org.zheng.demo.type;
 
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.JsonTypeName;
 
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeName("order") 
@JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME)
public class Order {
 
    private String productName;
 
    private int quantity;
 
    public Order() {
        super();
    }
 
    public Order(int quantity, String name) {
        super();
        this.quantity = quantity;
        this.productName = name;
    }
 
    public String getProductName() {
        return productName;
    }
 
    public int getQuantity() {
        return quantity;
    }
 
    public void setProductName(String name) {
        this.productName = name;
    }
 
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
 
}

5. Demo How to Map a Subset of Json Using Jackson


5.1 Read via JsonNode

In this step, I will create a Junit test class Jackson_Node_Test.java which utilizes the JsonNode tree model to parse the JSON and extract the subset of fields dynamically.

Jackson_Node_Test.java

package org.zheng.demo;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import org.junit.jupiter.api.Test;
import org.zheng.demo.data.Customer;
import org.zheng.demo.data.Order;
 
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
 
class Jackson_Node_Test {
 
    @Test
    void test_readJsonFromFile_via_readTree() {
        ObjectMapper ob = new ObjectMapper();
        File jsonFile = new File("src/test/resources/customer.json");
        try {
            JsonNode jsonNodes = ob.readTree(jsonFile);
 
            String name = jsonNodes.get("name").asText();
            int id = jsonNodes.get("id").asInt();
            Customer cust = new Customer(name, id);
 
            cust.setCustTag(jsonNodes.get("custTag").asText());
            cust.setEmail(jsonNodes.get("email").asText());
 
            List<Order> orders = new ArrayList<>();
            cust.setOrders(orders);
 
            JsonNode ordersNode = jsonNodes.get("orders");
            if (ordersNode.isArray()) {
                for (JsonNode orderNode : ordersNode) {
                    JsonNode nameNode = orderNode.get("productName");
                    JsonNode quantityNode = orderNode.get("quantity");
                    orders.add(new Order(quantityNode.asInt(), nameNode.asText()));
                }
            }
 
            assertEquals("major", cust.getCustTag());
            assertEquals("test@test.com", cust.getEmail());
            assertEquals("Zheng", cust.getName());
            assertEquals(30, cust.getId());
            assertEquals(2, cust.getOrders().size());
            assertEquals("test", cust.getOrders().get(0).getProductName());
 
            System.out.println(ob.writerWithDefaultPrettyPrinter().writeValueAsString(cust));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
}

◉ Line 22: create a file object from the src/test/resources/customer.json.
◉ Line 24: use the objectMapper.readTree to obtain the JsonNode.
◉ Line 26,27,28: parse the customer’s id and name data from JsonNode and create a customer object.
◉ Line 31, 32: parse the custTag and email data from JsonNode.
◉ Line 36-41: parse the orders from JsonNode.

Execute this Junit test – test_readJsonFromFile_via_readTree and capture the output.

test_readJsonFromFile_via_readTree output

{
  "custTag" : "major",
  "email" : "test@test.com",
  "id" : 30,
  "name" : "Zheng",
  "orders" : [ {
    "productName" : "test",
    "quantity" : 10
  }, {
    "productName" : "test",
    "quantity" : 1
  } ]
}

Note: as you see, the mapped customer object contains a subset of the original customer.json file.

5.2 Ignore Unknown Properties

In this step, I will create a Junit test class JacksonTest which uses @JsonIgnoreProperties(ignoreUnknown = true) and ObjectMapper to map a subset of JSON fields based on the Java POJO. There are two tests:

◉ test_readJsonFromFile_via – read the JSON from the customer.json file and map a subset into org.zheng.demo.data.Customer.
◉ test_readFile_withCustomizedRoot – read the JSON from the customerWithJsonType file and map a subset into org.zheng.demo.type.Customer.

JacksonTest.java

package org.zheng.demo;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
 
import org.junit.jupiter.api.Test;
import org.zheng.demo.data.Customer;
 
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
class JacksonTest {
 
    private ObjectMapper ob = new ObjectMapper();
 
    @Test
    void test_readFile_withCustomizedRoot() {
        File jsonFile = new File("src/test/resources/customerWithJsonType.json");
 
        try {
            org.zheng.demo.type.Customer readCust = ob.readValue(jsonFile, org.zheng.demo.type.Customer.class);
 
            assertEquals("major", readCust.getCustTag());
            assertEquals("test@test.com", readCust.getEmail());
            assertEquals("Zheng", readCust.getName());
            assertEquals(30, readCust.getId());
            assertEquals(2, readCust.getOrders().size());
            assertEquals("test", readCust.getOrders().get(0).getProductName());
             
            String jsonStr = ob.writerWithDefaultPrettyPrinter().writeValueAsString(readCust);
            System.out.println(jsonStr);
 
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
 
    @Test
    void test_readJsonFromFile_via() throws StreamReadException, DatabindException, IOException {
 
        try (InputStream inputStream = JacksonTest.class.getResourceAsStream("/customer.json")) {
            if (inputStream == null) {
                System.out.println("File not found");
                return;
            }
 
            Customer cust = ob.readValue(inputStream, Customer.class);
 
            assertEquals("major", cust.getCustTag());
            assertEquals("test@test.com", cust.getEmail());
            assertEquals("Zheng", cust.getName());
            assertEquals(30, cust.getId());
            assertEquals(2, cust.getOrders().size());
            assertEquals("test", cust.getOrders().get(0).getProductName());
 
            String jsonStr = ob.writerWithDefaultPrettyPrinter().writeValueAsString(cust);
            System.out.println(jsonStr);
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
 
}

◉ Line 22: read the JSON file from "src/test/resources/customerWithJsonType.json".
◉ Line 25: use the org.zheng.demo.type.Customer class when utilizing objectMapper.readValue method.
◉ Line 47: read the JSON file from "/customer.json"
◉ Line 53: use the org.zheng.demo.data.Customer class when calling objectMapper.readValue method.
◉ Line 34, 62: print out the subset mapped objects.
◉ Execute this Junit test and capture the output.

test_readJsonFromFile_via output

{
  "custTag" : "major",
  "email" : "test@test.com",
  "id" : 30,
  "name" : "Zheng",
  "orders" : [ {
    "productName" : "test",
    "quantity" : 10
  }, {
    "productName" : "test",
    "quantity" : 1
  } ]
}
{
  "customer" : {
    "custTag" : "major",
    "email" : "test@test.com",
    "id" : 30,
    "name" : "Zheng",
    "orders" : [ {
      "order" : {
        "productName" : "test",
        "quantity" : 10
      }
    }, {
      "order" : {
        "productName" : "test",
        "quantity" : 10
      }
    } ],
    "rewardPoint" : 100
  }
}

Note: as you see, only a subset of data are mapped. Also if the JSON has a customized root, then org.zheng.demo.type.Customer is used.

5.3 Handle the Wrap_oot

In this step, I will create a Junit Test class Jackson_wrapRootTest. It has two test methods that map a subset based on the default root.

◉ test_readFile_withWrapRoot – read a JSON string from customerWithWrapRoot.json file and map it with DeserializationFeature.UNWRAP_ROOT_VALUE.
◉ test_write_read_withRoot– verify the root is added to the JSON when SerializationFeature.WRAP_ROOT_VALUE is enabled.

Jackson_WrapRootTest.java

package org.zheng.demo;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import org.junit.jupiter.api.Test;
import org.zheng.demo.data.Customer;
import org.zheng.demo.data.Order;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
 
class Jackson_WrapRootTest {
 
    private ObjectMapper ob = new ObjectMapper();
 
    @Test
    void test_readFile_withWrapRoot() {
        File jsonFile = new File("src/test/resources/customerWithWrapRoot.json");
 
        try {
            Customer readCust = ob.readerFor(Customer.class).with(DeserializationFeature.UNWRAP_ROOT_VALUE)
                    .readValue(jsonFile);
 
            assertEquals("major", readCust.getCustTag());
            assertEquals("test@test.com", readCust.getEmail());
            assertEquals("Zheng", readCust.getName());
            assertEquals(30, readCust.getId());
            assertEquals(2, readCust.getOrders().size());
            assertEquals("test", readCust.getOrders().get(0).getProductName());
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
 
    @Test
    void test_write_read_withRoot() {
        Customer cust = new Customer("Zheng", 30);
        Order order = new Order();
        cust.setEmail("test@test.com");
        List orders = new ArrayList();
        order.setProductName("test");
        order.setQuantity(10);
        orders.add(order);
        orders.add(order);
        cust.setOrders(orders);
 
        try {
            String jsonString = ob.enable(SerializationFeature.WRAP_ROOT_VALUE).writeValueAsString(cust);
            System.out.println("Serialized JSON: " + jsonString);
 
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
 
    }
}

◉ Line 25: read JSON from customerWithWrapRoot.json.
◉ Line 28, 57: set DeserializationFeature.UNWRAP_ROOT_VALUE when reading and SerializationFeature.WRAP_ROOT_VALUE when writing.
Execute this Junit test and capture the output.

Jackson_wrapRootTest output

Serialized JSON: {"Customer":{"custTag":null,"email":"test@test.com","id":30,"name":"Zheng","orders":[{"productName":"test","quantity":10},{"productName":"test","quantity":10}]}}

Source: javacodegeeks.com