HOCON serializers
AbstractMenus uses its own wrappers over the Lightbend HOCON config library. A serializer is a small factory that takes a ConfigNode and returns a Java object.
Each serializer implements NodeSerializer<T>. The interface has one method: deserialize(Class<T> type, ConfigNode node).
When you need to register a serializer
Section titled “When you need to register a serializer”You usually don’t need to register a serializer manually. The five register(...) calls on the type registries (action, rule, item-property, activator, catalog) accept a NodeSerializer<S> and wire it into the shared NodeSerializers collection automatically.
The case where you do register a serializer manually: when you want to deserialize a custom parameter object used by your action or rule, so HOCON parsing can read it nested inside another structure via node.getValue(MyType.class).
api.serializers().register(MyType.class, new MyTypeSerializer());Do this from MenuExtension.onEnable(api). By the time onEnable returns for your addon, AbstractMenus is still pre-menu-load, so any types you register are available when menus parse.
Default serializers
Section titled “Default serializers”AbstractMenus ships serializers for Java primitives and a few common types out of the box:
BooleanIntegerLongFloatDoubleStringUUID
Plus its own value types: TypeBool, TypeInt, TypeDouble, TypeString, TypeMaterial, TypeLocation, TypeSlot, etc.
Example 1. Deserialize a simple object
Section titled “Example 1. Deserialize a simple object”public class User { public String name; public int age;}user { name: "Notch" age: 42}public class UserSerializer implements NodeSerializer<User> {
@Override public User deserialize(Class<User> type, ConfigNode node) throws NodeSerializeException { User user = new User(); user.name = node.node("name").getString(); user.age = node.node("age").getInt(); return user; }}ConfigNode is the parsed structure. The serializer reads named fields off node and copies them into the target type.
Example 2. Deserialize nested objects
Section titled “Example 2. Deserialize nested objects”If a field of your type is itself a serializable type, call getValue(SomeType.class) and let the registered serializer for SomeType do the work:
user { name: "Notch" age: 42 friend { name: "Alex" age: 38 }}public class User { public String name; public int age; public User friend;}public class UserSerializer implements NodeSerializer<User> {
@Override public User deserialize(Class<User> type, ConfigNode node) throws NodeSerializeException { User user = new User(); user.name = node.node("name").getString(); user.age = node.node("age").getInt(); user.friend = node.node("friend").getValue(User.class); return user; }}getValue(User.class) looks up the registered UserSerializer and runs it on the nested node. Make sure UserSerializer is registered before any HOCON that references it parses, otherwise getValue throws.
Example 3. Deserialize collections
Section titled “Example 3. Deserialize collections”HOCON supports lists. The API supports lists of any registered type.
user { name: "Notch" age: 42 friends: [ { name: "Petya", age: 34 }, { name: "Alex", age: 38 } ]}public class User { public String name; public int age; public List<User> friends;}public class UserSerializer implements NodeSerializer<User> {
@Override public User deserialize(Class<User> type, ConfigNode node) throws NodeSerializeException { User user = new User(); user.name = node.node("name").getString(); user.age = node.node("age").getInt(); user.friends = node.node("friends").getList(User.class); return user; }}getList(SomeType.class) works for any type that has a registered serializer, including primitives. node.getList(String.class) returns a List<String>.
ConfigNode shortcuts
Section titled “ConfigNode shortcuts”ConfigNode exposes the common reads you’d expect:
node.getString() // primitive stringnode.getString("default") // primitive string with defaultnode.getInt()node.getInt(0)node.getBoolean()node.getDouble()node.getList(String.class)node.isNull()node.isPrimitive()node.isMap()node.isList()
node.node("path") // child node by dotted path (one or many segments)node.child("name") // single-step child lookupnode.childrenList() // List<ConfigNode> for list nodesnode.childrenMap() // Map<String, ConfigNode> for map nodesnode.hasChildren()node.key() // name of this node in its parentnode.path() // full dotted path from rootnode.parent() // parent ConfigNode or nullnode.getValue(MyType.class) // run the registered serializernode.getValue(MyType.class, fallback)isNull() is the cheapest way to probe whether an optional field exists. The convention is: node.node("optional").isNull() ? defaultValue : node.node("optional").getInt().
For list and map nodes you’ll use childrenList() / childrenMap() constantly - they’re how the bundled serializers walk through items, actions, and bindings.