Jackson Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

An INI file is an initialization or configuration file for Windows or MS-DOS. They have plain text content which comprises key-value pairs in sections. While we may prefer to configure our applications with Java's native .properties files or other formats, we may sometimes need to consume data from existing INI files.

In this tutorial, we'll look at a couple of libraries that can help us. We'll also look at a way to populate a POJO with data from our INI files.

2. Creating a Sample INI File

Let's start with a sample INI file, sample.ini:

; for 16-bit app support
[fonts]
letter=bold
text-size=28

[background]
color=white

[RequestResult]
RequestCode=1

[ResponseResult]
ResultCode=0

This file has four sections, using a mixture of lower-case, kebab-case, and upper-camel-case for naming. It has values that are either strings or numeric.

3. Parsing an INI File Using ini4j

ini4j is a lightweight library for reading configuration out of INI files. It hasn't been updated since 2015.

3.1. Installing ini4j

To be able to use the ini4j library, first, we should add its dependency in our pom.xml:

<dependency>
    <groupId>org.ini4j</groupId>
    <artifactId>ini4j</artifactId>
    <version>0.5.4</version>
</dependency>

3.2. Opening an INI File in ini4j

We can open an INI file in ini4j by constructing an Ini object:

File fileToParse = new File("sample.ini");
Ini ini = new Ini(fileToParse);

This object now contains the sections and keys.

3.3. Reading a Section Key

We can read a key in a section from our INI file with the get() function on the Ini class:

assertThat(ini.get("fonts", "letter"))
  .isEqualTo("bold");

3.4. Converting to a Map

Let's see how easy it is to convert the whole INI file into Map<String, Map<String, String>>, which is a Java native data structure that represents the hierarchy of an INI file:

public static Map<String, Map<String, String>> parseIniFile(File fileToParse)
  throws IOException {
    Ini ini = new Ini(fileToParse);
    return ini.entrySet().stream()
      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}

Here, the entrySet of the Ini object is essentially a key-value pair of String and Map<String, String>. The internal representation of Ini is pretty much a Map, so it's easily converted into a plain Map by using stream() and the toMap() collector.

We can now read sections from this map with get():

assertThat(result.get("fonts").get("letter"))
  .isEqualTo("bold");

The Ini class is very easy to use out of the box, and converting to a Map may not be necessary, though we'll find a use for that later.

However, ini4j is an old library and does not look to be well-maintained. Let's consider another option.

4. Parsing an INI File Using Apache Commons

Apache Commons has a more sophisticated tool for processing INI files. This is capable of modeling the whole file for read and write, though we'll focus only on its parsing capabilities.

4.1. Installing Commons Configuration

Let's start by adding the required dependency in our pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-configuration2</artifactId>
    <version>2.8.0</version>
</dependency>

Version 2.8.0 was updated in 2022, which is more recent than ini4j.

4.2. Opening an INI File

We can open an INI file by declaring an INIConfiguration object and passing it a Reader:

INIConfiguration iniConfiguration = new INIConfiguration();
try (FileReader fileReader = new FileReader(fileToParse)) {
    iniConfiguration.read(fileReader);
}

Here we've used the try-with-resources pattern to open a FileReader and then asked the INIConfiguration object to read it with the read function.

4.3. Reading a Section Key

The INIConfiguration class has a getSection() function to read a section and a getProperty() function on the returned object to read a key:

String value = iniConfiguration.getSection("fonts")
  .getProperty("letter")
  .toString();
assertThat(value)
  .isEqualTo("bold");

We should note that getProperty() returns Object rather than String, so needs to be converted to a String.

4.4. Converting to Map

We can also convert the INIConfiguration into a Map as before. This is a bit more complex than with ini4j:

Map<String, Map<String, String>> iniFileContents = new HashMap<>();

for (String section : iniConfiguration.getSections()) {
    Map<String, String> subSectionMap = new HashMap<>();
    SubnodeConfiguration confSection = iniConfiguration.getSection(section);
    Iterator<String> keyIterator = confSection.getKeys();
    while (keyIterator.hasNext()) {
        String key = keyIterator.next();
        String value = confSection.getProperty(key).toString();
        subSectionMap.put(key, value);
    }
    iniFileContents.put(section, subSectionMap);
}

To get all of the sections, we need to use getSections() to find their names. Then getSection() can give us each section.

We can then use the Iterator that provides all the keys for the section and use getProperty() with each to get the key-value pairs.

While a Map is harder to produce here, the advantage of the plainer data structure is that we can hide the INI file parsing from other parts of the system. Or, we could convert the configuration into POJOs.

5. Convert INI File to POJO

We can use Jackson to convert our Map structure into a POJO. We can decorate our POJO with deserialization annotations to help Jackson make sense of the various naming conventions in the original INI file. A POJO is easier to use than any of the data structures we've seen so far.

5.1. Import Jackson

We need to add Jackson to our pom.xml:

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

5.2. Defining Some POJOs

The fonts section of our sample file uses kebab-case for its properties. Let's define a class to represent that section:

@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public static class Fonts {
    private String letter;
    private int textSize;

    // getters and setters
}

Here we've used the JsonNaming annotation to describe the case used in the properties.

Similarly, the RequestResult section has properties that use upper-camel-case:

@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
public static class RequestResult {
    private int requestCode;

    // getters and setters
}

The section names themselves are a variety of cases, so we can declare each section in our parent object, using the JsonProperty annotation to show deviations from the default lower-camel-case naming:

public class MyConfiguration {
    private Fonts fonts;
    private Background background;

    @JsonProperty("RequestResult")
    private RequestResult requestResult;

    @JsonProperty("ResponseResult")
    private ResponseResult responseResult;

    // getters and setters
}

5.3. Convert from Map to POJO

Now we have the ability to use either of our libraries to read an INI file as a Map and the ability to model the contents of the file as a POJO, we can use a Jackson ObjectMapper to perform the conversion:

ObjectMapper objectMapper = new ObjectMapper();
Map<String, Map<String, String>> iniKeys = parseIniFile(TEST_FILE);
MyConfiguration config = objectMapper.convertValue(iniKeys, MyConfiguration.class);

Let's check that the whole file was loaded correctly:

assertThat(config.getFonts().getLetter()).isEqualTo("bold");
assertThat(config.getFonts().getTextSize()).isEqualTo(28);
assertThat(config.getBackground().getColor()).isEqualTo("white");
assertThat(config.getRequestResult().getRequestCode()).isEqualTo(1);
assertThat(config.getResponseResult().getResultCode()).isZero();

We should note that numeric properties, such as textSize and requestCode have been loaded into our POJO as numbers.

6. Comparison of Libraries and Approaches

The ini4j library is very simple to use and is essentially a simple Map-like structure. However, this is an older library without regular updates.

The Apache Commons solution is fuller featured and has regular updates but requires a little more work to use.

7. Conclusion

In this article, we saw how to read INI files using a couple of open-source libraries. We saw how to read individual keys and how to iterate over the whole file to produce a Map.

Then we saw how we can convert from Map to POJO using Jackson.

As always, the example code is available over on GitHub.

Jackson bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Jackson footer banner
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!