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:
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()
}
}