Executing & Handling Codecs
Codecs define how an object is serialized, but they do not process the data themselves. To execute a codec, you must provide a DynamicOps implementation, which tells the codec what format (JSON, YAML, NBT, etc.) it should read from or write to.
DynamicOps Implementations
AbyssalLib provides several built-in format operations:
Implementation | Target Format |
|---|
JsonOps.INSTANCE
| Jackson JsonNode |
YamlOps.INSTANCE
| SnakeYAML / Bukkit YAML |
NbtOps.INSTANCE
| Minecraft Tag (CompoundTag, ListTag, etc.) |
ByteOps.INSTANCE
| byte[] (Custom binary format)
|
StringOps.INSTANCE
| String (Inline literal representation)
|
Encoding and Decoding
When you run a codec, it does not immediately return your object or throw an exception. Instead, it returns a DataResult<T>, a monadic container that safely wraps either the successful value, partial successes (with warnings), or a detailed structural error.
To convert raw serialized data back into a Java object, use decode.
JsonNode json = getJsonFromSomewhere();
// Pass the operations instance and the raw input
DataResult<PlayerStats> result = PlayerStats.CODEC.decode(JsonOps.INSTANCE, json);
if (result.isSuccess()) {
PlayerStats stats = result.getOrThrow();
System.out.println("Loaded: " + stats.kills());
} else {
// Gracefully handle the error without try/catch blocks
System.err.println("Failed to load stats: " + result.error().get());
}
To convert a Java object into serialized data, use encode.
PlayerStats stats = new PlayerStats(150, 12);
// Encode the object into a Jackson JsonNode
DataResult<JsonNode> result = PlayerStats.CODEC.encode(JsonOps.INSTANCE, stats);
if (result.isSuccess()) {
JsonNode json = result.getOrThrow();
saveToFile(json);
}
If you are decoding massive configurations or chunk data, you can execute the codec off the main thread natively using the async variants. These return a standard CompletableFuture.
PlayerStats.CODEC.decodeAsync(JsonOps.INSTANCE, largeJsonInput).thenAccept(result -> {
if (result.isSuccess()) {
System.out.println("Decoded successfully in the background!");
}
});
Handling DataResults
Because data structures can be deeply nested, DataResult acts as a safe container. It traces the exact path of failures (e.g., Missing required field: 'config.weapons.damage') and allows you to provide fallbacks easily.
DataResult<Weapon> result = Weapon.CODEC.decode(YamlOps.INSTANCE, yamlNode);
// 1. Throw an exception if it fails (Classic behavior)
Weapon weapon = result.getOrThrow();
// 2. Provide a default fallback if it fails
Weapon safeWeapon = result.orElse(Weapon.DEFAULT);
// 3. Check for partial successes (Data loaded, but warnings were logged)
if (result.isPartial()) {
Weapon loaded = result.getOrThrow();
for (DataError warning : result.warnings()) {
plugin.getLogger().warning("Non-fatal issue: " + warning.message());
}
}
// 4. Map the result directly if successful
DataResult<Integer> damageResult = result.map(Weapon::getDamage);
The Dynamic Wrapper
If you need to quickly extract, modify, or convert a specific nested value from a raw serialized object without writing a full RecordCodec, you can wrap it in a Dynamic<T>.
The Dynamic object binds the raw data to its DynamicOps, allowing fluent path querying and conversions.
JsonNode rawJson = getLargeConfigFile();
// Wrap the JSON in a Dynamic instance
Dynamic<JsonNode> dynamic = new Dynamic<>(JsonOps.INSTANCE, rawJson);
// Safely query nested paths
dynamic.get("server.database.port").ifPresent(portNode -> {
int port = Codecs.INT.decode(JsonOps.INSTANCE, portNode.value()).orElse(3306);
System.out.println("Database Port: " + port);
});
// Convert the entire JSON structure into NBT format instantly
Dynamic<Tag> nbtDynamic = dynamic.convert(NbtOps.INSTANCE);
05 June 2026