Serializing a single item. java.lang.Byte

I’m still learning how to make sponge plugins, so require some guidance.

Have the following code:

public class getHand implements CommandExecutor {
    @Override
    public CommandResult execute(CommandSource src, CommandContext args) throws CommandException{
        TypeSerializers.getDefaultSerializers();
        if(!(src instanceof Player)) {
            src.sendMessage(Text.of("Can only be run by players"));
            return CommandResult.success();
        }
        Player player = (Player) src;
        ItemStack item = player.getItemInHand(HandTypes.MAIN_HAND).get();
        try {
            String serializeItem = serializeItem(item);
            player.sendMessage(Text.of(serializeItem));
        } catch (IOException | ObjectMappingException err) {
            player.sendMessage(Text.of(new Object[]{TextColors.RED, "Error: ", err.getMessage()}));
        }
        return CommandResult.success();
    }
    public static String serializeItem(ItemStack item) throws ObjectMappingException, IOException {
        ConfigurationNode node = GsonConfigurationLoader.builder().build().createEmptyNode();
        StringWriter stringWriter = new StringWriter();
        node.setValue(TypeToken.of(ItemStack.class), item);
        GsonConfigurationLoader.builder().setSink(() -> {
            return new BufferedWriter(stringWriter);
        }).setIndent(0).build().save(node);
        return stringWriter.toString();
    }
}

It’s just to get a string containing item information. Works for most items, however when faced with a pokecube containing a pokemon, I get “java.lang.IllegalArgumentException: Configuration does not accept objects of type class java.lang.Byte”

Same error can be seen here:

However, I’m having problems understanding what @BrainStone did to fix the problem. I took a brief look at his code, to see what the fix was, but it seems to have changed so much.

First, a few unrelated things:

This does nothing at all.

success() implies that it was successful. If there was an error, throw a CommandException. In fact, that applies even more in your catch block, since CommandException's constructor can take a Throwable.

Uh, the point of a vararg is that you don’t have to explicitly state new Object[]{...}. Text.of(TextColors.RED, "Error: ", err.getMessage()) works just fine.

You could just as easily do () -> new BufferedWriter(stringWriter).

And, for the actual question, ItemStackSnapshot serializes just fine. I believe it is a Configurate error that alternate numeric types like byte cannot be serialized.

Do you know if the issues in the linked thread have since been resolved?

How can I fix it? I’ve also tried it on some books given to the player as a guide, with links, colors, indexed pages, etc, and when trying to serialize it, it gave me the same error.
Normal written books and other pokecube items work fine.

And yea, I know a lot of my code is bad thanks for going through it like you did. Couldn’t find too many guides online to help me out.

public static String serializeItem(ItemStackSnapshot item) throws ObjectMappingException, IOException {
    ConfigurationNode node = GsonConfigurationLoader.builder().build().createEmptyNode();
    StringWriter stringWriter = new StringWriter();
    node.setValue(TypeToken.of(ItemStackSnapshot.class), item);
    GsonConfigurationLoader.builder().setSink(() -> new BufferedWriter(stringWriter)).setIndent(0).build().save(node);
    return stringWriter.toString();
}

Same issue whether I use ItemStack or ItemStackSnapshot

Well, if you don’t mind doing it in a slightly hacky way:

public class ByteSerializer implements TypeSerializer<Byte> {
    public Byte deserialize(TypeToken<?> type, ConfigurationNode value) throws ObjectMappingException {
        return (byte) value.getInt();
    }
    public void serialize(TypeToken<?> type, Byte obj, ConfigurationNode value) throws ObjectMappingException {
        value.setValue(obj.intValue());
    }
}

//...

ConfigurationOptions.defaults().getSerializers().registerType(TypeToken.of(Byte.class), new ByteSerializer());

I’m really confused as to what’s going on and how to use that. :disappointed_relieved:
Guess there’s still much for me to learn before making a plugin.

So whenever you’re trying to write a value to the config, there’s a fixed set of things which are natively supported. Things like ints or Strings or ConfigurationNodes. For a more complex value, it uses a TypeSerializer to figure out how to write it to the config. This class tells it what to do with a Byte object (i.e. convert it to an int and write it, or read it as an int and convert it). And the line of code at the bottom would be put in a pre-init listener so that all configs will be able to serialize/deserialize Bytes.

Am I correct in saying that you are learning Java at the same time as learning Sponge?

I learnt java in highschool, but it was very basic. Mostly work with websites, so lot of php and javascript.
Was trying to make something simple, just serialize, store or edit it, and deserialize. Something many plugins need to do with items, entities, and more; so assumed sponge would be able to take care of everything more easily. Something like Item.JsonSerialize(args);, and that’d be it. To serialize normal items it wasn’t too complex, there are many examples and can better follow along, had no idea I’d run into this problem.

I put this following on my main class.

@Listener
public void preInt(GamePreInitializationEvent event){
    ConfigurationOptions.defaults().getSerializers().registerType(TypeToken.of(Byte.class), new ByteSerializer());
}

And added a ByteSerializer with the same code provided. Anything more I need to do or did I do something wrong?

Looks good. You should definitely try some Java courses, to up your skill level. Sponge is an advanced API, and to be able to use it properly you’re going to want to know 100% of the language and a good chunk of the standard library (IO and collections are a must).

As for your comment, well, people often do have the need to serialize things like that; that’s why anything implementing DataHolder (like items or entities) has a method toContainer() that turns it into a DataContainer. Then, there’s classes to convert that to ConfigurationNode, classes to save it to JSON or HOCON or NBT, methods to query the inside of it, etc. And a TypeSerializer for any DataHolders, so that they can be inserted directly into an existing ConfigurationNode. But Java is a high-level, object-oriented language; objects have methods detailing their own functionality, and don’t need a hundred convenience/glue methods to save you from having a couple extra method calls; you simply mix and match the existing functionalities.

You have sort of gone the long way around, though.

public static String serializeItem(ItemStackSnapshot item) throws IOException {
    return DataFormats.JSON.write(item.toContainer());
}

It didn’t work. So must have done something wrong or am missing something.
I didn’t know there was a shorter way to do it, saw other plugins and people asking doing it this way; so took it from there.

Well, tried the shorter way. Got “Unable to translate object to JSON” when trying to serialize those items.

… Huh. Any sort of indication as to why?
Also, what version of Sponge are you using?

Using spongeforge-1.12-2387-7.0.0-BETA-2431.jar

Hmm, just tried on 1.11 and got the tutorial books on boot that I couldn’t serialize. Worked on the books.
But I still can’t serialize the pokecubes, getting the following error:

Error occurred while executing command 'gethand' for source EntityPlayerMP['Tridaak'/225, l='world', x=-70.72, y=73.00, z=266.14]: Unable to translate object to JSON: [[email protected]
java.lang.IllegalArgumentException: Unable to translate object to JSON: [[email protected]
        at org.spongepowered.common.data.persistence.JsonDataFormat.write(JsonDataFormat.java:223) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.writeMap(JsonDataFormat.java:245) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.write(JsonDataFormat.java:217) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.writeMap(JsonDataFormat.java:245) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.write(JsonDataFormat.java:217) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.writeMap(JsonDataFormat.java:245) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.write(JsonDataFormat.java:217) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.writeMap(JsonDataFormat.java:245) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.write(JsonDataFormat.java:217) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.writeView(JsonDataFormat.java:199) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.writeTo(JsonDataFormat.java:190) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.data.persistence.JsonDataFormat.write(JsonDataFormat.java:183) ~[JsonDataFormat.class:1.11.2-2393-6.1.0-BETA-2471]
        at getHand.serializeItem(getHand.java:46) ~[getHand.class:?]
        at getHand.execute(getHand.java:38) ~[getHand.class:?]
        at org.spongepowered.api.command.spec.CommandSpec.process(CommandSpec.java:359) ~[CommandSpec.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.api.command.dispatcher.SimpleDispatcher.process(SimpleDispatcher.java:336) ~[SimpleDispatcher.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.command.SpongeCommandManager.process(SpongeCommandManager.java:296) ~[SpongeCommandManager.class:1.11.2-2393-6.1.0-BETA-2471]
        at net.minecraft.command.ServerCommandManager.func_71556_a(SourceFile:1083) ~[bd.class:?]
        at net.minecraft.network.NetHandlerPlayServer.func_147361_d(NetHandlerPlayServer.java:904) ~[mi.class:?]
        at net.minecraft.network.NetHandlerPlayServer.func_147354_a(NetHandlerPlayServer.java:883) ~[mi.class:?]
        at net.minecraft.network.play.client.CPacketChatMessage.func_148833_a(SourceFile:37) ~[ip.class:?]
        at net.minecraft.network.play.client.CPacketChatMessage.func_148833_a(SourceFile:9) ~[ip.class:?]
        at org.spongepowered.common.network.PacketUtil.lambda$onProcessPacket$0(PacketUtil.java:145) ~[PacketUtil.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.event.tracking.CauseTracker.switchToPhase(CauseTracker.java:161) [CauseTracker.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.network.PacketUtil.onProcessPacket(PacketUtil.java:144) [PacketUtil.class:1.11.2-2393-6.1.0-BETA-2471]
        at net.minecraft.network.PacketThreadUtil$1.redirect$onProcessPacket$zjm000(SourceFile:539) [fo$1.class:?]
        at net.minecraft.network.PacketThreadUtil$1.run(SourceFile:13) [fo$1.class:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_111]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_111]
        at net.minecraft.util.Util.func_181617_a(SourceFile:46) [h.class:?]
        at org.spongepowered.common.SpongeImplHooks.onUtilRunTask(SpongeImplHooks.java:256) [SpongeImplHooks.class:1.11.2-2393-6.1.0-BETA-2471]
        at net.minecraft.server.MinecraftServer.redirect$onRun$zhl000(MinecraftServer.java:3953) [MinecraftServer.class:?]
        at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:679) [MinecraftServer.class:?]
        at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:384) [lh.class:?]
        at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:624) [MinecraftServer.class:?]
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:482) [MinecraftServer.class:?]
        at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]

Switched to the old code, same error as before

[06:53:13] [Server thread/ERROR] [Sponge]: Error occurred while executing command 'gethand' for source EntityPlayerMP['Tridaak'/323, l='world', x=-82.81, y=76.00, z=267.86]: Configuration does not accept objects of type class java.lang.Byte
java.lang.IllegalArgumentException: Configuration does not accept objects of type class java.lang.Byte
        at ninja.leaping.configurate.ScalarConfigValue.setValue(ScalarConfigValue.java:42) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.insertNewValue(SimpleConfigurationNode.java:274) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:201) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:182) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:182) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:182) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:182) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:182) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.SimpleConfigurationNode.setValue(SimpleConfigurationNode.java:39) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.config.DataSerializableTypeSerializer.serialize(DataSerializableTypeSerializer.java:59) ~[DataSerializableTypeSerializer.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.config.DataSerializableTypeSerializer.serialize(DataSerializableTypeSerializer.java:41) ~[DataSerializableTypeSerializer.class:1.11.2-2393-6.1.0-BETA-2471]
        at ninja.leaping.configurate.ConfigurationNode.setValue(ConfigurationNode.java:402) ~[spongeforge-1.11.2-2393-6.1.0-BETA-2471.jar:1.11.2-2393-6.1.0-BETA-2471]
        at getHand.serializeItem(getHand.java:48) ~[getHand.class:?]
        at getHand.execute(getHand.java:38) ~[getHand.class:?]
        at org.spongepowered.api.command.spec.CommandSpec.process(CommandSpec.java:359) ~[CommandSpec.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.api.command.dispatcher.SimpleDispatcher.process(SimpleDispatcher.java:336) ~[SimpleDispatcher.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.command.SpongeCommandManager.process(SpongeCommandManager.java:296) ~[SpongeCommandManager.class:1.11.2-2393-6.1.0-BETA-2471]
        at net.minecraft.command.ServerCommandManager.func_71556_a(SourceFile:1083) ~[bd.class:?]
        at net.minecraft.network.NetHandlerPlayServer.func_147361_d(NetHandlerPlayServer.java:904) ~[mi.class:?]
        at net.minecraft.network.NetHandlerPlayServer.func_147354_a(NetHandlerPlayServer.java:883) ~[mi.class:?]
        at net.minecraft.network.play.client.CPacketChatMessage.func_148833_a(SourceFile:37) ~[ip.class:?]
        at net.minecraft.network.play.client.CPacketChatMessage.func_148833_a(SourceFile:9) ~[ip.class:?]
        at org.spongepowered.common.network.PacketUtil.lambda$onProcessPacket$0(PacketUtil.java:145) ~[PacketUtil.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.event.tracking.CauseTracker.switchToPhase(CauseTracker.java:161) [CauseTracker.class:1.11.2-2393-6.1.0-BETA-2471]
        at org.spongepowered.common.network.PacketUtil.onProcessPacket(PacketUtil.java:144) [PacketUtil.class:1.11.2-2393-6.1.0-BETA-2471]
        at net.minecraft.network.PacketThreadUtil$1.redirect$onProcessPacket$zjm000(SourceFile:539) [fo$1.class:?]
        at net.minecraft.network.PacketThreadUtil$1.run(SourceFile:13) [fo$1.class:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_111]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_111]
        at net.minecraft.util.Util.func_181617_a(SourceFile:46) [h.class:?]
        at org.spongepowered.common.SpongeImplHooks.onUtilRunTask(SpongeImplHooks.java:256) [SpongeImplHooks.class:1.11.2-2393-6.1.0-BETA-2471]
        at net.minecraft.server.MinecraftServer.redirect$onRun$zhl000(MinecraftServer.java:3953) [MinecraftServer.class:?]
        at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:679) [MinecraftServer.class:?]
        at net.minecraft.server.dedicated.DedicatedServer.func_71190_q(DedicatedServer.java:384) [lh.class:?]
        at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:624) [MinecraftServer.class:?]
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:482) [MinecraftServer.class:?]
        at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]

This is almost definitely a Pixelmon problem, then. Not sure how you’d go about solving it.

It looks to me like spongeforge isn’t properly serialising one of the rarer nbt data types.

The genetics in pokecube (actually from thutcore) are stored as various tags, depending on the gene, but include:
booleans
byte[]s
int[]s
Strings
bytes
floats
ints

as well as some taglists of some the above, and of course nbttagcompounds including those.

there is then some other stuff stored as the above, as well as some longs.

It all serializes properly with the vanilla stuff, and the following command even works for giving me one:
/give @p pokecube:pokecube 1 0 {display:{cubecolor:15597568,Name:"RaticateȲ"},Po - Pastebin.com Where I did PokecubeMod.log(stack.getTagCompound()+""); to get the tag to put in that command.

I do no think this is a problem with the item, it is somehow a problem with the serializer being used, maybe if I can get some free time I can try to take a look at it.

It’s a quirk of Configurate, actually, specifically the Gson loader - this is something I’ve known about for a while.

When creating an empty node, note that Configurate sets the accepted types here. From the list above, there are three types that aren’t accepted:

  • bytes
  • shorts
  • arrays

You can work around the problem with bytes and shorts by just adding them to the accepted types, either just override the loader and the createEmptyNode() method, or force the accepted types.

Arrays, on the other hand, are harder - Configurate works with collections fine, but because of how arrays work, Configurate is in no way set up to handle that. I tried adding arrays to the accepted types function, as well as creating serialisers, and I struggled to get it to work - though I might have missed something - I didn’t spend too long on it.

I’ve had the same problem with users trying to create kits in Nucleus with items that have arrays in their NBT, and I don’t have an answer for it right now, as I use Configurate to store the kit data in Json. I have ideas, but it may involve just delving into the internals of Configurate rather than creating an addon library to try to support this.

However, it’s also very much worth keeping in mind that because JSON/HOCON just store numbers, rather than different types of numbers, if you serialise a byte, for example, you’ll probably get it back as an integer or a long. I found this when serialising enchantments a while ago, I serialised a short, but upon deserialisation, it was handed back as an integer - which caused this issue. I had to write some custom code to “fix” this in my code.

So, to summarise, I know exactly what the problems are. The solutions are going to be harder to come across I think, we may need to coordinate with @zml to try to iron out some of these issues - it might end up being a case of PRing array support into Configurate (somehow) and adding a loader that appends the expected number type to keys that should deserialise to bytes or shorts, or lists that should deserialise to arrays. Ugly solutions I know, I’m hoping we can do better.

That said, does DataFormats.JSON work OK as a workaround?