Interacting with a JSON file using Jackson library

Interacting with a JSON file using Jackson library

Jackson library is the backbone of many interactions with any JSON structures in Java world. Jackson has many useful features for working with JSON. One of them is serializing and deserializing JSON to/from a file. In this article, we explain how to interact with a JSON file using Jackson library.

Add the dependency

The process is very simple. First, we need to add the fasterXML/Jackson dependency to the project:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

Create a helper class

After that, we need to initialize the ObjectMapper class. To ease the process, we will create a rudimentary helper class to open a file, read JSON values, and save the changes as follows,

package com.madadipouya.test.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

public class JacksonHelper<T> {

    private final File jsonFile;

    private ObjectMapper objectMapper;

    public JacksonHelper(String fileName) {
        this(new File(Runner.class.getClassLoader().getResource(fileName).getFile()));
    }

    public JacksonHelper(Path path) {
        this(path.toFile());
    }

    private JacksonHelper(File jsonFile) {
        this.jsonFile = jsonFile;
        objectMapper = new ObjectMapper();
    }

    public T read(Class<T> clazz) throws JacksonException {
        try {
            return objectMapper.readValue(jsonFile, clazz);
        } catch (IOException e) {
            throw new JacksonException("Failed to read json from the file", e);
        }
    }

    public void write(T object) throws JacksonException {
        try {
            objectMapper.writeValue(jsonFile, object);
        } catch(IOException e) {
            throw new JacksonException("Failed to write json value to file", e);
        }
    }
}

Keep in mind that for the read method, we still need to pass the deserialization type even though the helper is instantiated with the given type via generic. That’s sadly one of the Java generic shortcomings which erases the type. Hence, it’s impossible to determine the class type with generic.

Code JSON structure as Java class

Now let’s find a JSON file and add it to the project. We found a relatively complex JSON example from Hackers and Slackers, which is:

{
  "destination_addresses": [
    "Philadelphia, PA, USA"
  ],
  "origin_addresses": [
    "New York, NY, USA"
  ],
  "rows": [
    {
      "elements": [
        {
          "distance": {
            "text": "94.6 mi",
            "value": 152193
          },
          "duration": {
            "text": "1 hour 44 mins",
            "value": 6227
          },
          "status": "OK"
        }
      ]
    }
  ],
  "status": "OK"
}

Once everything is ready, let’s create a (DTO) class that ObjectMapper uses to deserialize the JSON file. Our class looks like below,

package com.madadipouya.test.jackson;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public class Location {

    @JsonProperty("destination_addresses")
    private List<String> destinationAddresses;

    @JsonProperty("origin_addresses")
    private List<String> originAddresses;

    private List<Row> rows;

    private String status;

    public List<String> getDestinationAddresses() {
        return destinationAddresses;
    }

    public void setDestinationAddresses(List<String> destinationAddresses) {
        this.destinationAddresses = destinationAddresses;
    }

    public List<String> getOriginAddresses() {
        return originAddresses;
    }

    public void setOriginAddresses(List<String> originAddresses) {
        this.originAddresses = originAddresses;
    }

    public List<Row> getRows() {
        return rows;
    }

    public void setRows(List<Row> rows) {
        this.rows = rows;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }


    public static class Row {
        List<Element> elements;

        public List<Element> getElements() {
            return elements;
        }

        public void setElements(List<Element> elements) {
            this.elements = elements;
        }
    }

    public static class Element {

        private Distance distance;

        private Duration duration;

        private String status;

        public Distance getDistance() {
            return distance;
        }

        public void setDistance(Distance distance) {
            this.distance = distance;
        }

        public Duration getDuration() {
            return duration;
        }

        public void setDuration(Duration duration) {
            this.duration = duration;
        }

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }
    }

    public static class Distance extends TextValue {

        public Distance() {
            super();
        }
    }

    public static class Duration extends TextValue {

        public Duration() {
            super();
        }
    }

    public static class TextValue {

        private String text;

        private long value;

        public TextValue() {
            text = "";
            value = 0;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public long getValue() {
            return value;
        }

        public void setValue(long value) {
            this.value = value;
        }
    }
}

Read/write values from/to the JSON file

The next step is to use the helper class to load the file and deserialize it to the class we created previously with Jackson library.

package com.madadipouya.test.jackson;

public class Runner {

    private static final String MESSAGE = "The distance from '%s' to '%s' is '%s' and takes '%s' to reach there.";

    public static void main(String[] args) throws JacksonException {

        JacksonHelper<Location> helper = new JacksonHelper<>("example.json");
        Location jsonValues = helper.read(Location.class);

        System.out.print(String.format(MESSAGE, jsonValues.getOriginAddresses().get(0),
                jsonValues.getDestinationAddresses().get(0),
                jsonValues.getRows().get(0).getElements().get(0).getDistance().getText(),
                jsonValues.getRows().get(0).getElements().get(0).getDuration().getText()));

        jsonValues.getOriginAddresses().add("Texas, TX, USA");

        helper.write(jsonValues);
    }
}

Now that the content of the file is loaded, we should be able to freely modify it. See line 17 of the above snippet.

The last step is to persist the changes to the file. Again we rely on the helper class to serialize the object to the file using the write method. See line 19.

Conclusion

In this article, we discovered how to interact with a JSON file using the Jackson library. We created a helper class to open a JSON file, read the content, and deserialize it to a JSON object. Then using the same helper class, we persisted the changes to the file and serialized the changes again.

If you are interested to know how to achieve a similar thing with the Gson library, check out how to convert nested Json string to Java object using Gson library article.

Inline/featured images credits