Skip to content

Loading and Saving Plain Old Java Objects (POJOs)

A POJO is a straightforward type with no references to any particular frameworks. No special functions or inheritances are required.

Introduction

As an example, here is what your config class might look like.

import blue.endless.jankson.Comment;
import java.util.Map;
import java.util.Set;

public class Config {
    // Comment annotations populate serialized keys with json5 comments
    @Comment("Why orbs?")
    public int max_orbs = 10;

    // Collection and map types are properly captured in both directions.
    private List<String> some_strings = List.of("abc", "cde");

    // While it's always best to have the object initialized in the constructor or initializer,
    // as long as a full, concrete type is declared, Jankson can create the instance and fill it in.
    protected Map<String, String> uninitialized = null;
    protected Map<String, String> initialized = Map.of("a", "1", "foo", "bar");

    public Integer[] integer_array = new Integer[] { 6, 9 };
    public int[] int_array = new int[] { 4, 2, 0 };

    public NestedConfig nested = new NestedConfig();

    // Nested configurations with inner classes are also possible
    public static class NestedConfig {
        public Set<Short> nested_data = Set.of((short)1, (short)2, (short)3);
    }
}
import blue.endless.jankson.Comment
import java.util.Map
import java.util.Set

data class Config(
    // Comment annotations populate serialized keys with json5 comments
    @Comment("Why orbs?")
    var max_orbs: Integer = 10,

    // Collection and map types are properly captured in both directions.
    private var some_strings: List<String> = listOf("abc", "cde"),

    // While it's always best to have the object initialized in the constructor or initializer,
    // as long as a full, concrete type is declared, Jankson can create the instance and fill it in.
    protected var uninitialized: Map<String, String>? = null,
    protected var initialized: Map<String, String> = mapOf("a" to "1", "foo" to "bar"),

    val integer_array: IntArray = arrayOf(6, 9),
    val int_array: IntArray = arrayOf(4, 2, 0).toIntArray(),

    val nested: NestedConfig = NestedConfig()
) {
    // Nested configurations with inner classes are also possible
    data class NestedConfig(
        val nested_data: Set<Short> = setOf(1, 2, 3)
    )
}
import blue.endless.jankson.Comment
import java.util.Map
import java.util.Set

class Config {
    /* Comment annotations populate serialized keys with json5 comments */
    @Comment("Why orbs?")
    int max_orbs = 10

    // Collection and map types are properly captured in both directions.
    private List<String> some_strings = List.of("abc", "cde");

    // While it's always best to have the object initialized in the constructor or initializer,
    // as long as a full, concrete type is declared, Jankson can create the instance and fill it in.
    protected Map<String, String> uninitialized = null
    protected Map<String, String> initialized = Map.of("a", "1", "foo", "bar")

    Integer[] integer_array = [ 6, 9 ]
    int[] int_array = [ 4, 2, 0 ]

    NestedConfig nested = new NestedConfig()

    // Nested configurations with inner classes are also possible
    class NestedConfig {
        Set<Short> nested_data = Set.of((short)1, (short)2, (short)3)
    }
}

When we're finished here, this POJO will be able to be serialized into and deserialized out of this JSON5 format:

{
  /* why orbs? */
  "maxOrbs": 10,
  "some_strings": [],
  "uninitialized": null,
  "initialized": {
    "a": "1",
    "foo": "bar"
  },
  "integer_array": [6, 9],
  "int_array": [4, 2, 0],
  "nested": {
    "nested_data": [1, 2, 3]
  }
}

Loading from a file

import blue.endless.jankson.Jankson;
import blue.endless.jankson.JsonObject;
import java.io.File;

class Config {
    // Variables described above...

    // Other code...

    public static Config loadConfig() {
        // Create a new Jankson instance
        // (This can also be a static instance, defined outside the function)
        var jankson = Jankson.builder()
            // You can register adapters here to customize deserialization
            //.registerTypeAdapter(...)
            // Likewise, you can customize serializer behavior
            //.registerSerializer(...)
            // In most cases, the default Jankson is all you need.
            .build();
        // Parse the config file into a JSON Object
        try {
            File configFile = new File("config.json");
            JsonObject configJson = jankson.load(configFile);
            // Convert the raw object into your POJO type
            return jankson.fromJson(configJson, Config.class);
        } catch (IOException | SyntaxError e) {
            e.printStackTrace();
            return new Config(); // You could also throw a RuntimeException instead
        }
    }
}
import blue.endless.jankson.Jankson

class Config(
    // Variables described above
) {
    // Other code...

    fun loadConfig(): Config? {
        // Create a new Jankson instance
        // (This can also be a static instance, defined outside the function)
        val jankson = Jankson.builder()
            // You can register adapters here to customize deserialization
            //.registerTypeAdapter(...)
            // Likewise, you can customize serializer behavior
            //.registerSerializer(...)
            // In most cases, the default Jankson is all you need.
            .build();
        // Parse the config file into a JSON Object
        val configFile = File("config.json")
        val configJson: JsonObject = jankson.load(configFile)
        // Convert the raw object into your POJO type.
        return jankson.fromJson(configJson, ConfigObject::class.java)
    }
}
import blue.endless.jankson.Jankson

class Config {
    // Variables described above...

    // Other code...

    static Config loadConfig() {
        // Create a new Jankson instance
        // (This can also be a static instance, defined outside the function)
        Jankson jankson = Jankson.builder()
            // You can register adapters here to customize deserialization
            //.registerTypeAdapter(...)
            // Likewise, you can customize serializer behavior
            //.registerSerializer(...)
            // In most cases, the default Jankson is all you need.
            .build()
        // Parse the config file into a JSON Object
        File configFile = new File("config.json")
        JsonObject configJson = jankson.load(configFile)
        // Convert the raw object into your POJO type.
        return jankson.fromJson(configJson, ConfigObject.class)
    }
}

Saving to a file

import blue.endless.jankson.Jankson;
import java.io.File;

class Config {
    // Variables described above...

    // Other code...

    public File saveConfig() {
        var configFile = new File("config.json");
        var jankson = Jankson.builder().build();
        var result = jankson
                .toJson(this)        // The first call makes a JsonObject
                .toJson(true, true); // The second turns the JsonObject into a String

        try {
            var fileIsUsable = configFile.exists() || configFile.createNewFile();
            if (!fileIsUsable) return null;
            var out = new FileOutputStream(configFile, false);

            out.write(result.getBytes());
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return configFile;
    }
}
import blue.endless.jankson.Jankson

internal class Config {
    // Variables described above...

    // Other code...

    fun saveConfig(): File? {
        val configFile = File("config.json")
        val jankson: Unit = Jankson.builder().build()
        val result: Unit = jankson
            .toJson(this)       // The first call makes a JsonObject
            .toJson(true, true) // The second turns the JsonObject into a String
        try {
            val fileIsUsable = configFile.exists() || configFile.createNewFile()
            if (!fileIsUsable) return null
            val out = FileOutputStream(configFile, false)
            out.write(result.getBytes())
            out.flush()
            out.close()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return configFile
    }
}
import blue.endless.jankson.Jankson

class Config {
    // Variables described above...

    // Other code...

    File saveConfig() {
        def configFile = new File("config.json")
        def jankson = Jankson.builder().build()
        def result = jankson
                .toJson(this)       // The first call makes a JsonObject
                .toJson(true, true) // The second turns the JsonObject into a String

        try {
            def fileIsUsable = configFile.exists() || configFile.createNewFile()
            if (!fileIsUsable) return null
            def out = new FileOutputStream(configFile, false)

            out.write(result.getBytes())
            out.flush()
            out.close()
        } catch (IOException e) {
            e.printStackTrace()
        }

        return configFile
    }
}

Generics

But these functions can be genericized to be used with any POJO. Just make the functions static and take a type argument.

Or in Kotlin, you can use a reified type argument.

import blue.endless.jankson.Jankson;
import java.io.File;

// Take a generic type argument to represent any loadable POJO
inline fun <reified T> loadConfig(fileName: String): T {
    val jankson = Jankson.builder().build()
    val configFile = File(fileName)
    val configJson = jankson.load(configFile)
    return jankson.fromJson(configJson, T::class.java)
}

// Saving is more of the same.
// We just bake the config down to a String and write it out.
inline fun <reified T> saveConfig(pojo: T, fileName: String): File {
    val configFile = configDirectory.resolve(fileName)
    val jankson = Jankson.builder().build()
    val result = jankson
        .toJson(pojo)
        .toJson(true, true)

    try {
        if (!configFile.exists()) configFile.createNewFile()
        val out = FileOutputStream(configFile, false)

        out.write(result.toByteArray())
        out.flush()
        out.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}