package org.example.examplemod.mixin; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(PlayerEntity.class) public abstract class PlayerEntityMixin { @Unique public List myList = new ArrayList<>(); // I recommend you read writeMyDataToNbt first before trying to understand readCustomDataFromNbt @Inject(method = "readCustomDataFromNbt", at = @At("TAIL")) public void readMyDataFromNbt(NbtCompound playerNbt, CallbackInfo ci) { // read our serialized list NbtList myListNbt = playerNbt.getList("MyList", NbtElement.COMPOUND_TYPE); // iterate over its items for (int i = 0; i < myListNbt.size(); i++) { // construct the object we need to deserialize MyObject myObject = new MyObject(); // read the serialized object NbtCompound myObjectNbt = myListNbt.getCompound(i); // read properties that are supported and write them to our object myObject.mySupportedObject = myObjectNbt.getInt("MySupportedObject"); // if mySupportedList was int[], we could've just used getIntArray, but since it's a List, we have to do something else Arrays.stream(myObjectNbt.getIntArray("MySupportedList")).forEach(myInteger -> myObject.mySupportedList.add(myInteger)); // read properties that aren't supported MyAnotherObject myAnotherObject = new MyAnotherObject(); NbtCompound myNotSupportedObjectNbt = myObjectNbt.getCompound("MyNotSupportedObject"); myAnotherObject.myUuid = myNotSupportedObjectNbt.getUuid("MyUUID"); // write them to our object myObject.myNotSupportedObject = myAnotherObject; // finally, add our constructed object to our list myList.add(myObject); } } @Inject(method = "writeCustomDataToNbt", at = @At("TAIL")) public void writeMyDataToNbt(NbtCompound playerNbt, CallbackInfo ci) { // create a new NBT record for our list NbtCompound myListNbt = new NbtCompound(); // you can use a regular for-loop if you want, there's no difference. FabricMC wiki uses Iterable#forEach so that's why it's used here myList.forEach(myObject -> { // create a new NBT record for our object NbtCompound myObjectNbt = new NbtCompound(); // write properties that are supported myObjectNbt.putInt("MySupportedObject", myObject.mySupportedObject); myObjectNbt.putIntArray("MySupportedList", myObject.mySupportedList); // write myNotSupportedObject, which doesn't have a method for reading/writing in NbtCompound NbtCompound myNotSupportedObjectNbt = new NbtCompound(); myNotSupportedObjectNbt.putUuid("MyUUID", myObject.myNotSupportedObject.myUuid); // you can nest NBT records infinitely myObjectNbt.put("MyNotSupportedObject", myNotSupportedObjectNbt); // put our newly created record into the list record myListNbt.put("MyObject", myObjectNbt); }); // finally, put our list record into the player's NBT playerNbt.put("MyList", myListNbt); } } // these classes are in the same file for simplicity sake. you should make them public and put them in their own files class MyObject { // the NbtCompound class has methods to write and read this object - putInt and getInt public int mySupportedObject = 0; // there are even some supported lists! this one uses putIntArray public List mySupportedList = new ArrayList<>(); // if there's no method in NbtCompound to handle your class, // you'll have to write their properties separately and, when reading, construct the objects using the written properties // just how are we currently doing with this object public MyAnotherObject myNotSupportedObject; } class MyAnotherObject { public UUID myUuid; }