Practical examples demonstrating Aether Datafixers in real-world scenarios.
A comprehensive example showing player data migration across multiple versions:
- TypeReferences Pattern - Centralized type identifiers
- Schema Classes - Version-specific type definitions
- DataFix Implementations - Migration logic
- Bootstrap Creation - Wiring it all together
- Complete Example - Full working code
Migrating user profile data with nested objects and optional fields.
Migrating application configuration files with defaults and restructuring.
Handling polymorphic data with TaggedChoice for different entity types.
// 1. Define type reference
TypeReference PLAYER = TypeReference.of("player");
// 2. Create bootstrap
DataFixerBootstrap bootstrap = new DataFixerBootstrap() {
@Override
public void registerSchemas(SchemaRegistry schemas) {
schemas.register(new DataVersion(1), parent -> {
Schema schema = new Schema(new DataVersion(1), parent);
schema.registerType(PLAYER, DSL.and(
DSL.field("name", DSL.string()),
DSL.remainder()
));
return schema;
});
}
@Override
public void registerFixes(FixRegistrar fixes) {
// No fixes for single version
}
};
// 3. Create fixer
AetherDataFixer fixer = new DataFixerRuntimeFactory()
.create(new DataVersion(1), bootstrap);public class RenameFieldFix extends SchemaDataFix {
public RenameFieldFix(SchemaRegistry schemas) {
super(schemas, new DataVersion(1), new DataVersion(2), "rename-field");
}
@Override
protected TypeRewriteRule makeRule(Schema inputSchema, Schema outputSchema) {
return Rules.renameField(TypeReferences.PLAYER, "oldName", "newName");
}
}@Override
protected TypeRewriteRule makeRule(Schema inputSchema, Schema outputSchema) {
return Rules.addField(
TypeReferences.PLAYER,
"newField",
player -> player.createInt(100) // Default value
);
}@Override
protected TypeRewriteRule makeRule(Schema inputSchema, Schema outputSchema) {
return Rules.transformField(
TypeReferences.PLAYER,
"gameMode",
mode -> {
int modeInt = mode.asInt().orElse(0);
String modeStr = switch (modeInt) {
case 0 -> "survival";
case 1 -> "creative";
default -> "unknown";
};
return mode.createString(modeStr);
}
);
}@Override
protected TypeRewriteRule makeRule(Schema inputSchema, Schema outputSchema) {
return Rules.seq(
Rules.renameField(TypeReferences.PLAYER, "hp", "health"),
Rules.renameField(TypeReferences.PLAYER, "xp", "experience"),
Rules.addField(TypeReferences.PLAYER, "level", p -> p.createInt(1))
);
}@Override
protected TypeRewriteRule makeRule(Schema inputSchema, Schema outputSchema) {
return Rules.transform(TypeReferences.PLAYER, player -> {
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);
Dynamic<?> position = player.emptyMap()
.set("x", player.createDouble(x))
.set("y", player.createDouble(y))
.set("z", player.createDouble(z));
return player
.remove("x").remove("y").remove("z")
.set("position", position);
});
}my-game/
├── src/main/java/
│ └── com/example/game/
│ ├── data/
│ │ ├── TypeReferences.java # Type identifiers
│ │ ├── GameDataBootstrap.java # Bootstrap
│ │ ├── schemas/
│ │ │ ├── Schema100.java # v1.0.0 schema
│ │ │ ├── Schema110.java # v1.1.0 schema
│ │ │ └── Schema200.java # v2.0.0 schema
│ │ └── fixes/
│ │ ├── PlayerV1ToV2Fix.java
│ │ └── PlayerV2ToV3Fix.java
│ └── GameApplication.java
└── pom.xml
The examples module in the repository contains runnable examples:
cd aether-datafixers-examples
mvn compile exec:java -Dexec.mainClass="de.splatgames.aether.datafixers.examples.game.GameExample"