This guide shows how to change the structure of your data - nesting, flattening, merging, and splitting objects.
Group related flat fields into a nested object:
// Before: {"x": 100, "y": 64, "z": -200}
// After: {"position": {"x": 100, "y": 64, "z": -200}}
return Rules.transform(TypeReferences.PLAYER, player -> {
// Extract values
double x = player.get("x").asDouble().orElse(0.0);
double y = player.get("y").asDouble().orElse(0.0);
double z = player.get("z").asDouble().orElse(0.0);
// Create nested object
Dynamic<?> position = player.emptyMap()
.set("x", player.createDouble(x))
.set("y", player.createDouble(y))
.set("z", player.createDouble(z));
// Update structure
return player
.remove("x").remove("y").remove("z")
.set("position", position);
});Extract nested fields to the top level:
// Before: {"position": {"x": 100, "y": 64, "z": -200}}
// After: {"x": 100, "y": 64, "z": -200}
return Rules.transform(TypeReferences.PLAYER, player -> {
Dynamic<?> position = player.get("position").orElseEmptyMap();
double x = position.get("x").asDouble().orElse(0.0);
double y = position.get("y").asDouble().orElse(0.0);
double z = position.get("z").asDouble().orElse(0.0);
return player
.remove("position")
.set("x", player.createDouble(x))
.set("y", player.createDouble(y))
.set("z", player.createDouble(z));
});Combine multiple nested objects into one:
// Before: {"position": {"x": 100}, "rotation": {"yaw": 90}}
// After: {"transform": {"x": 100, "yaw": 90}}
return Rules.transform(TypeReferences.ENTITY, entity -> {
Dynamic<?> position = entity.get("position").orElseEmptyMap();
Dynamic<?> rotation = entity.get("rotation").orElseEmptyMap();
Dynamic<?> transform = entity.emptyMap()
.set("x", position.get("x").orElse(entity.createDouble(0.0)))
.set("y", position.get("y").orElse(entity.createDouble(0.0)))
.set("z", position.get("z").orElse(entity.createDouble(0.0)))
.set("yaw", rotation.get("yaw").orElse(entity.createFloat(0.0f)))
.set("pitch", rotation.get("pitch").orElse(entity.createFloat(0.0f)));
return entity
.remove("position")
.remove("rotation")
.set("transform", transform);
});Divide one nested object into multiple:
// Before: {"transform": {"x": 100, "y": 64, "yaw": 90}}
// After: {"position": {"x": 100, "y": 64}, "rotation": {"yaw": 90}}
return Rules.transform(TypeReferences.ENTITY, entity -> {
Dynamic<?> transform = entity.get("transform").orElseEmptyMap();
Dynamic<?> position = entity.emptyMap()
.set("x", transform.get("x").orElse(entity.createDouble(0.0)))
.set("y", transform.get("y").orElse(entity.createDouble(0.0)))
.set("z", transform.get("z").orElse(entity.createDouble(0.0)));
Dynamic<?> rotation = entity.emptyMap()
.set("yaw", transform.get("yaw").orElse(entity.createFloat(0.0f)))
.set("pitch", transform.get("pitch").orElse(entity.createFloat(0.0f)));
return entity
.remove("transform")
.set("position", position)
.set("rotation", rotation);
});Change the structure of items in a list:
// Before: [{"id": "a", "count": 1}, {"id": "b", "count": 2}]
// After: [{"item": {"id": "a"}, "quantity": 1}, ...]
return Rules.transform(TypeReferences.INVENTORY, inv -> {
return inv.update("items", items ->
items.updateList(item -> {
String id = item.get("id").asString().orElse("");
int count = item.get("count").asInt().orElse(1);
Dynamic<?> itemObj = item.emptyMap()
.set("id", item.createString(id));
return item.emptyMap()
.set("item", itemObj)
.set("quantity", item.createInt(count));
})
);
});Add a wrapper object around data:
// Before: {"name": "Steve", "level": 10}
// After: {"data": {"name": "Steve", "level": 10}, "version": 2}
return Rules.transform(TypeReferences.SAVE, save -> {
// The current save becomes the "data" field
Dynamic<?> data = save;
return save.emptyMap()
.set("data", data)
.set("version", save.createInt(2));
});Remove a wrapper object:
// Before: {"data": {"name": "Steve", "level": 10}, "version": 2}
// After: {"name": "Steve", "level": 10}
return Rules.transform(TypeReferences.SAVE, save -> {
return save.get("data").orElseEmptyMap();
});Move a field from one nested path to another:
// Before: {"player": {"config": {"volume": 80}}}
// After: {"settings": {"audio": {"volume": 80}}}
return Rules.transform(TypeReferences.SAVE, save -> {
// Extract the value
int volume = save.get("player").get("config").get("volume")
.asInt().orElse(100);
// Build new structure
Dynamic<?> audio = save.emptyMap()
.set("volume", save.createInt(volume));
Dynamic<?> settings = save.emptyMap()
.set("audio", audio);
// Remove old, add new
return save
.update("player", player -> player.remove("config"))
.set("settings", settings);
});Change from map structure to list:
// Before: {"items": {"sword": 1, "apple": 64}}
// After: {"items": [{"id": "sword", "count": 1}, {"id": "apple", "count": 64}]}
return Rules.transform(TypeReferences.INVENTORY, inv -> {
Dynamic<?> itemsMap = inv.get("items").orElseEmptyMap();
// Convert map entries to list items
List<Dynamic<?>> itemsList = new ArrayList<>();
itemsMap.asMap().result().orElse(Map.of()).forEach((key, value) -> {
String id = key.asString().orElse("");
int count = value.asInt().orElse(1);
Dynamic<?> item = inv.emptyMap()
.set("id", inv.createString(id))
.set("count", inv.createInt(count));
itemsList.add(item);
});
Dynamic<?> listDynamic = inv.emptyList();
for (Dynamic<?> item : itemsList) {
listDynamic = listDynamic.append(item);
}
return inv.set("items", listDynamic);
});Change from list to map structure:
// Before: {"items": [{"id": "sword", "count": 1}, {"id": "apple", "count": 64}]}
// After: {"items": {"sword": 1, "apple": 64}}
return Rules.transform(TypeReferences.INVENTORY, inv -> {
Dynamic<?> itemsMap = inv.emptyMap();
inv.get("items").asStream().orElse(Stream.empty()).forEach(item -> {
String id = item.get("id").asString().orElse("");
int count = item.get("count").asInt().orElse(1);
// Note: itemsMap must be updated in place or accumulated differently
});
// Build the map
Dynamic<?> finalMap = inv.emptyMap();
for (Dynamic<?> item : inv.get("items").asList().orElse(List.of())) {
String id = item.get("id").asString().orElse("");
int count = item.get("count").asInt().orElse(1);
finalMap = finalMap.set(id, inv.createInt(count));
}
return inv.set("items", finalMap);
});- Extract helper methods for complex restructuring
- Handle missing data with
orElsedefaults - Test thoroughly with sample data
- Document the before/after structure in comments