YetAnotherConfigLib

YetAnotherConfigLib

13M Downloads

Missing json sub fields (or adding new config sub fields) crashes config screen due to deserializing with gson without default constructors

kevinthegreat1 opened this issue · 6 comments

commented

What I wanted

I have an existing config class:

    public static class QuickNavItem {
        @SerialEntry
        public boolean render = true;

        @SerialEntry
        public String uiTitle;

        @SerialEntry
        public String clickEvent;
    }

which serializes as such:

      "render": true,
      "uiTitle": "Your Skills",
      "clickEvent": "/skills"

I want to add a new field so my config looks like this:

    public static class QuickNavItem {
        @SerialEntry
        public boolean render = true;

        @SerialEntry
        public String uiTitle;

        @SerialEntry
        public String tooltip;

        @SerialEntry
        public String clickEvent;
    }

What I tried

I've tried setting a default value to the tooltip field, adding required = false, or adding nullable = true.

What I encountered

All of the options above fails to load the old config file with the following error:

[23:46:20] [Render thread/ERROR] (Minecraft) Reported exception thrown!
net.minecraft.util.crash.CrashException: mouseClicked event handler
	at net.minecraft.client.gui.screen.Screen.wrapScreenError(Screen.java:467) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.Mouse.onMouseButton(Mouse.java:96) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.Mouse.method_22686(Mouse.java:192) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.util.thread.ThreadExecutor.execute(ThreadExecutor.java:90) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.Mouse.method_22684(Mouse.java:192) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at org.lwjgl.glfw.GLFWMouseButtonCallbackI.callback(GLFWMouseButtonCallbackI.java:43) ~[lwjgl-glfw-3.3.3.jar:?]
	at org.lwjgl.system.JNI.invokeV(Native Method) ~[lwjgl-3.3.3.jar:?]
	at org.lwjgl.glfw.GLFW.glfwWaitEventsTimeout(GLFW.java:3509) ~[lwjgl-glfw-3.3.3.jar:?]
	at com.mojang.blaze3d.systems.RenderSystem.limitDisplayFPS(RenderSystem.java:160) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.MinecraftClient.render(MinecraftClient.java:1295) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.MinecraftClient.run(MinecraftClient.java:884) [minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.main.Main.main(Main.java:228) [minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470) [fabric-loader-0.15.11.jar:?]
	at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) [fabric-loader-0.15.11.jar:?]
	at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) [fabric-loader-0.15.11.jar:?]
	at net.fabricmc.devlaunchinjector.Main.main(Main.java:86) [dev-launch-injector-0.2.1+build.8.jar:?]
Caused by: java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "this.inputField" is null
	at dev.isxander.yacl3.gui.controllers.string.StringControllerElement.getValueText(StringControllerElement.java:461) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.controllers.string.StringControllerElement.setDimension(StringControllerElement.java:436) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.controllers.string.StringControllerElement.<init>(StringControllerElement.java:45) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.controllers.string.IStringController.provideWidget(IStringController.java:42) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.OptionListWidget.refreshOptions(OptionListWidget.java:75) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.OptionListWidget.<init>(OptionListWidget.java:40) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.YACLScreen$CategoryTab.<init>(YACLScreen.java:359) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at dev.isxander.yacl3.gui.YACLScreen.lambda$init$4(YACLScreen.java:89) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[?:?]
	at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[?:?]
	at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[?:?]
	at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[?:?]
	at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[?:?]
	at dev.isxander.yacl3.gui.YACLScreen.init(YACLScreen.java:90) ~[yet-another-config-lib-3.5.0+1.21-fabric.jar:?]
	at net.minecraft.client.gui.screen.Screen.init(Screen.java:324) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.MinecraftClient.setScreen(MinecraftClient.java:1134) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at com.terraformersmc.modmenu.gui.widget.entries.ModListEntry.openConfig(ModListEntry.java:120) ~[modmenu-11.0.0-beta.1.jar:?]
	at com.terraformersmc.modmenu.gui.widget.entries.ModListEntry.mouseClicked(ModListEntry.java:110) ~[modmenu-11.0.0-beta.1.jar:?]
	at com.terraformersmc.modmenu.gui.widget.ModListWidget.mouseClicked(ModListWidget.java:270) ~[modmenu-11.0.0-beta.1.jar:?]
	at net.minecraft.client.gui.ParentElement.mouseClicked(ParentElement.java:45) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	at net.minecraft.client.Mouse.method_1611(Mouse.java:96) ~[minecraft-merged-f6269c2542-1.21-loom.mappings.1_21.layered+hash.998484755-v2.jar:?]
	... 16 more
commented

I figured it out. First, this is a sub class, so this is serialized and deserialized with gson. Turns out having a constructor does matter. It means that there is no default constructor, and gson skips initialization if there are no default constructors available, which means the default values do not apply.

Solution

Have default values for the fields.
And either:

  1. Have no constructor.
  2. Have a default constructor available (one with no args).

Future steps

I don't think there's much yacl van do about this, except warn users about this behavior.

commented

Originally reported SkyblockerMod/Skyblocker#890 and here SkyblockerMod/Skyblocker#894.

In general, we've encountered many problems when the config simply refuses to load and crashes when it tries to deserialize a null or nonexistent field. I believe there should be alternative options such as default values instead of crashing.

commented

I have no affiliation with that man and yet I agree with all he says /j

commented

I also have no affiliation with that man and yet I agree with all he says

commented

Is a default option not specified by just giving the field an initial value?

commented

Is a default option not specified by just giving the field an initial value?

No. I also do have a constructor for this class, and I’ve tried both adding a default value to the field and making sure the constructor doesn’t set a null value. But to my knowledge, the constructor shouldn’t get called while deserializing from json?