From 46223c89765a1cad95e74b795cabb4435e0166e1 Mon Sep 17 00:00:00 2001
From: AnimeGitB <AnimeGitB@bigblueball.in>
Date: Thu, 11 Aug 2022 17:54:59 +0930
Subject: [PATCH] Refactor Json helper functions to JsonUtils

---
 .../java/emu/grasscutter/Grasscutter.java     |  8 +-
 .../grasscutter/config/ConfigContainer.java   |  4 +-
 .../java/emu/grasscutter/data/DataLoader.java |  7 +-
 .../emu/grasscutter/data/ResourceLoader.java  | 29 +++----
 .../game/activity/PlayerActivityData.java     |  5 +-
 .../musicgame/MusicGameActivityHandler.java   |  5 +-
 .../net/packet/PacketOpcodesUtils.java        |  6 +-
 .../emu/grasscutter/plugin/PluginManager.java |  3 +-
 .../server/http/dispatch/DispatchHandler.java | 10 +--
 .../java/emu/grasscutter/utils/JsonUtils.java | 77 +++++++++++++++++++
 .../java/emu/grasscutter/utils/Language.java  |  2 +-
 .../java/emu/grasscutter/utils/Utils.java     | 66 +---------------
 12 files changed, 117 insertions(+), 105 deletions(-)
 create mode 100644 src/main/java/emu/grasscutter/utils/JsonUtils.java

diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java
index 0f3827ab..520ebb34 100644
--- a/src/main/java/emu/grasscutter/Grasscutter.java
+++ b/src/main/java/emu/grasscutter/Grasscutter.java
@@ -12,7 +12,6 @@ import emu.grasscutter.command.PermissionHandler;
 import emu.grasscutter.config.ConfigContainer;
 import emu.grasscutter.data.ResourceLoader;
 import emu.grasscutter.database.DatabaseManager;
-import emu.grasscutter.net.packet.PacketOpcodesUtils;
 import emu.grasscutter.plugin.PluginManager;
 import emu.grasscutter.plugin.api.ServerHook;
 import emu.grasscutter.scripts.ScriptLoader;
@@ -27,6 +26,7 @@ import emu.grasscutter.server.http.handlers.GenericHandler;
 import emu.grasscutter.server.http.handlers.LogHandler;
 import emu.grasscutter.tools.Tools;
 import emu.grasscutter.utils.Crypto;
+import emu.grasscutter.utils.JsonUtils;
 import emu.grasscutter.utils.Language;
 import emu.grasscutter.utils.StartupArguments;
 import emu.grasscutter.utils.Utils;
@@ -202,7 +202,7 @@ public final class Grasscutter {
 
         // If the file already exists, we attempt to load it.
         try {
-            config = Utils.loadJsonToClass(configFile.getPath(), ConfigContainer.class);
+            config = JsonUtils.loadToClass(configFile.getPath(), ConfigContainer.class);
         } catch (Exception exception) {
             getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
             System.exit(1);
@@ -218,7 +218,7 @@ public final class Grasscutter {
         if (config == null) config = new ConfigContainer();
 
         try (FileWriter file = new FileWriter(configFile)) {
-            file.write(Utils.jsonEncode(config));
+            file.write(JsonUtils.encode(config));
         } catch (IOException ignored) {
             Grasscutter.getLogger().error("Unable to write to config file.");
         } catch (Exception e) {
@@ -272,7 +272,7 @@ public final class Grasscutter {
 
     @Deprecated(forRemoval = true)
     public static Gson getGsonFactory() {
-        return Utils.getGsonFactory();
+        return JsonUtils.getGsonFactory();
     }
 
     public static HttpServer getHttpServer() {
diff --git a/src/main/java/emu/grasscutter/config/ConfigContainer.java b/src/main/java/emu/grasscutter/config/ConfigContainer.java
index f6e84a7d..8a68976d 100644
--- a/src/main/java/emu/grasscutter/config/ConfigContainer.java
+++ b/src/main/java/emu/grasscutter/config/ConfigContainer.java
@@ -4,7 +4,7 @@ import com.google.gson.JsonObject;
 import emu.grasscutter.Grasscutter;
 import emu.grasscutter.Grasscutter.ServerDebugMode;
 import emu.grasscutter.Grasscutter.ServerRunMode;
-import emu.grasscutter.utils.Utils;
+import emu.grasscutter.utils.JsonUtils;
 
 import java.util.Set;
 import java.lang.reflect.Field;
@@ -26,7 +26,7 @@ public class ConfigContainer {
      */
     public static void updateConfig() {
         try { // Check if the server is using a legacy config.
-            JsonObject configObject = Utils.loadJsonToClass(Grasscutter.configFile.getPath(), JsonObject.class);
+            JsonObject configObject = JsonUtils.loadToClass(Grasscutter.configFile.getPath(), JsonObject.class);
             if (!configObject.has("version")) {
                 Grasscutter.getLogger().info("Updating legacy ..");
                 Grasscutter.saveConfig(null);
diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java
index d7f1a7c7..f2b18ee9 100644
--- a/src/main/java/emu/grasscutter/data/DataLoader.java
+++ b/src/main/java/emu/grasscutter/data/DataLoader.java
@@ -4,6 +4,7 @@ import emu.grasscutter.Grasscutter;
 import emu.grasscutter.server.http.handlers.GachaHandler;
 import emu.grasscutter.tools.Tools;
 import emu.grasscutter.utils.FileUtils;
+import emu.grasscutter.utils.JsonUtils;
 import emu.grasscutter.utils.Utils;
 
 import static emu.grasscutter.config.Configuration.DATA;
@@ -72,19 +73,19 @@ public class DataLoader {
 
     public static <T> T loadClass(String resourcePath, Class<T> classType) throws IOException {
         try (InputStreamReader reader = loadReader(resourcePath)) {
-            return Utils.loadJsonToClass(reader, classType);
+            return JsonUtils.loadToClass(reader, classType);
         }
     }
 
     public static <T> List<T> loadList(String resourcePath, Class<T> classType) throws IOException {
         try (InputStreamReader reader = loadReader(resourcePath)) {
-            return Utils.loadJsonToList(reader, classType);
+            return JsonUtils.loadToList(reader, classType);
         }
     }
 
     public static <T1,T2> Map<T1,T2> loadMap(String resourcePath, Class<T1> keyType, Class<T2> valueType) throws IOException {
         try (InputStreamReader reader = loadReader(resourcePath)) {
-            return Utils.loadJsonToMap(reader, keyType, valueType);
+            return JsonUtils.loadToMap(reader, keyType, valueType);
         }
     }
 
diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java
index 56aa89df..b5d51707 100644
--- a/src/main/java/emu/grasscutter/data/ResourceLoader.java
+++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java
@@ -14,6 +14,7 @@ import emu.grasscutter.game.world.SpawnDataEntry;
 import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId;
 import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
 import emu.grasscutter.scripts.SceneIndexManager;
+import emu.grasscutter.utils.JsonUtils;
 import emu.grasscutter.utils.Utils;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import lombok.SneakyThrows;
@@ -119,7 +120,7 @@ public class ResourceLoader {
 
     @SuppressWarnings({"rawtypes", "unchecked"})
     protected static <T> void loadFromResource(Class<T> c, String fileName, Int2ObjectMap map) throws Exception {
-        List<T> list = Utils.loadJsonToList(RESOURCE("ExcelBinOutput/" + fileName), c);
+        List<T> list = JsonUtils.loadToList(RESOURCE("ExcelBinOutput/" + fileName), c);
 
         for (T o : list) {
             GameResource res = (GameResource) o;
@@ -149,7 +150,7 @@ public class ResourceLoader {
             }
 
             try {
-                config = Utils.loadJsonToClass(file.getPath(), ScenePointConfig.class);
+                config = JsonUtils.loadToClass(file.getPath(), ScenePointConfig.class);
             } catch (Exception e) {
                 e.printStackTrace();
                 continue;
@@ -163,7 +164,7 @@ public class ResourceLoader {
             for (Map.Entry<String, JsonElement> entry : config.points.entrySet()) {
                 int id = Integer.parseInt(entry.getKey());
                 String name = sceneId + "_" + entry.getKey();
-                PointData pointData = Utils.jsonDecode(entry.getValue(), PointData.class);
+                PointData pointData = JsonUtils.decode(entry.getValue(), PointData.class);
                 pointData.setId(id);
 
                 GameData.getScenePointIdList().add(id);
@@ -181,7 +182,7 @@ public class ResourceLoader {
 
         // Read from cached file if exists
         try {
-            embryoList = Utils.loadJsonToList(DATA("AbilityEmbryos.json"), AbilityEmbryoEntry.class);
+            embryoList = JsonUtils.loadToList(DATA("AbilityEmbryos.json"), AbilityEmbryoEntry.class);
         } catch (Exception ignored) {}
 
         if (embryoList == null) {
@@ -208,7 +209,7 @@ public class ResourceLoader {
                 }
 
                 try {
-                    config = Utils.loadJsonToClass(file.getPath(), AvatarConfig.class);
+                    config = JsonUtils.loadToClass(file.getPath(), AvatarConfig.class);
                 } catch (Exception e) {
                     e.printStackTrace();
                     continue;
@@ -224,7 +225,7 @@ public class ResourceLoader {
             }
 
             try {
-                GameDepot.setPlayerAbilities(Utils.loadJsonToMap(RESOURCE("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json"), String.class, AvatarConfig.class));
+                GameDepot.setPlayerAbilities(JsonUtils.loadToMap(RESOURCE("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json"), String.class, AvatarConfig.class));
             } catch (Exception e) {
                 e.printStackTrace();
             }
@@ -253,7 +254,7 @@ public class ResourceLoader {
             List<AbilityConfigData> abilityConfigList;
 
             try {
-                abilityConfigList = Utils.loadJsonToList(file.getPath(), AbilityConfigData.class);
+                abilityConfigList = JsonUtils.loadToList(file.getPath(), AbilityConfigData.class);
             } catch (Exception e) {
                 e.printStackTrace();
                 continue;
@@ -311,7 +312,7 @@ public class ResourceLoader {
             // Load spawn entries from file
             try (InputStreamReader reader = DataLoader.loadReader(name)) {
                 // Add spawns to group if it already exists in our spawn group map
-                spawnEntryMap.addAll(Utils.loadJsonToList(reader, SpawnGroupEntry.class));
+                spawnEntryMap.addAll(JsonUtils.loadToList(reader, SpawnGroupEntry.class));
             } catch (Exception ignored) {}
         }
 
@@ -342,7 +343,7 @@ public class ResourceLoader {
         List<OpenConfigEntry> list = null;
 
         try {
-            list = Utils.loadJsonToList(DATA("OpenConfig.json"), OpenConfigEntry.class);
+            list = JsonUtils.loadToList(DATA("OpenConfig.json"), OpenConfigEntry.class);
         } catch (Exception ignored) {}
 
         if (list == null) {
@@ -363,7 +364,7 @@ public class ResourceLoader {
                     Map<String, OpenConfigData[]> config;
 
                     try {
-                        config = Utils.loadJsonToMap(file.getPath(), String.class, OpenConfigData[].class);
+                        config = JsonUtils.loadToMap(file.getPath(), String.class, OpenConfigData[].class);
                     } catch (Exception e) {
                         e.printStackTrace();
                         continue;
@@ -400,7 +401,7 @@ public class ResourceLoader {
             MainQuestData mainQuest = null;
 
             try {
-                mainQuest = Utils.loadJsonToClass(file.getPath(), MainQuestData.class);
+                mainQuest = JsonUtils.loadToClass(file.getPath(), MainQuestData.class);
             } catch (Exception e) {
                 e.printStackTrace();
                 continue;
@@ -434,7 +435,7 @@ public class ResourceLoader {
         for (File file : folder.listFiles()) {
             ScriptSceneData sceneData;
             try {
-                sceneData = Utils.loadJsonToClass(file.getPath(), ScriptSceneData.class);
+                sceneData = JsonUtils.loadToClass(file.getPath(), ScriptSceneData.class);
             } catch (Exception e) {
                 e.printStackTrace();
                 continue;
@@ -457,7 +458,7 @@ public class ResourceLoader {
             }
             try {
                 var sceneId = Integer.parseInt(matcher.group(1));
-                var data = Utils.loadJsonToClass(filename, HomeworldDefaultSaveData.class);
+                var data = JsonUtils.loadToClass(filename, HomeworldDefaultSaveData.class);
                 GameData.getHomeworldDefaultSaveData().put(sceneId, data);
             } catch (Exception ignored) {}
         });
@@ -472,7 +473,7 @@ public class ResourceLoader {
                 return;
             }
             try {
-                var data = Utils.loadJsonToClass(file.getFileName().toString(), SceneNpcBornData.class);
+                var data = JsonUtils.loadToClass(file.getFileName().toString(), SceneNpcBornData.class);
                 if (data.getBornPosList() == null || data.getBornPosList().size() == 0) {
                     return;
                 }
diff --git a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java
index 033157aa..3b71f6f6 100644
--- a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java
+++ b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java
@@ -3,7 +3,6 @@ package emu.grasscutter.game.activity;
 import dev.morphia.annotations.Entity;
 import dev.morphia.annotations.Id;
 import dev.morphia.annotations.Transient;
-import emu.grasscutter.Grasscutter;
 import emu.grasscutter.data.GameData;
 import emu.grasscutter.data.common.ItemParamData;
 import emu.grasscutter.data.excels.ActivityWatcherData;
@@ -13,7 +12,7 @@ import emu.grasscutter.game.player.Player;
 import emu.grasscutter.game.props.ActionReason;
 import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
 import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
-import emu.grasscutter.utils.Utils;
+import emu.grasscutter.utils.JsonUtils;
 import lombok.AccessLevel;
 import lombok.Builder;
 import lombok.Data;
@@ -69,7 +68,7 @@ public class PlayerActivityData {
     }
 
     public void setDetail(Object detail){
-        this.detail = Utils.jsonEncode(detail);
+        this.detail = JsonUtils.encode(detail);
     }
 
     public void takeWatcherReward(int watcherId) {
diff --git a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java
index 4c0930af..82fbef27 100644
--- a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java
+++ b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java
@@ -1,6 +1,5 @@
 package emu.grasscutter.game.activity.musicgame;
 
-import emu.grasscutter.Grasscutter;
 import emu.grasscutter.game.activity.ActivityHandler;
 import emu.grasscutter.game.activity.GameActivity;
 import emu.grasscutter.game.activity.PlayerActivityData;
@@ -8,7 +7,7 @@ import emu.grasscutter.game.props.ActivityType;
 import emu.grasscutter.net.proto.ActivityInfoOuterClass;
 import emu.grasscutter.net.proto.MusicBriefInfoOuterClass;
 import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
-import emu.grasscutter.utils.Utils;
+import emu.grasscutter.utils.JsonUtils;
 
 import java.util.stream.Collectors;
 
@@ -48,7 +47,7 @@ public class MusicGameActivityHandler extends ActivityHandler {
             playerActivityData.save();
         }
 
-        return Utils.jsonDecode(playerActivityData.getDetail(), MusicGamePlayerData.class);
+        return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
     }
 
     public boolean setMusicGameRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtils.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtils.java
index 009aa940..0b5e32fb 100644
--- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtils.java
+++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodesUtils.java
@@ -1,10 +1,8 @@
 package emu.grasscutter.net.packet;
 
-import java.io.BufferedWriter;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.lang.reflect.Field;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -12,7 +10,7 @@ import java.util.stream.Collectors;
 
 import emu.grasscutter.GameConstants;
 import emu.grasscutter.Grasscutter;
-import emu.grasscutter.utils.Utils;
+import emu.grasscutter.utils.JsonUtils;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 
@@ -60,7 +58,7 @@ public class PacketOpcodesUtils {
                     .filter(e -> e.getIntKey() > 0)
                     .collect(Collectors.toMap(Int2ObjectMap.Entry::getIntKey, Int2ObjectMap.Entry::getValue, (k, v) -> v, TreeMap::new));
             // Write to file
-            writer.write(Utils.jsonEncode(packetIds));
+            writer.write(JsonUtils.encode(packetIds));
             Grasscutter.getLogger().info("Dumped packet ids.");
         } catch (IOException e) {
             e.printStackTrace();
diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java
index 72edc9df..31bbf501 100644
--- a/src/main/java/emu/grasscutter/plugin/PluginManager.java
+++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java
@@ -2,6 +2,7 @@ package emu.grasscutter.plugin;
 
 import emu.grasscutter.Grasscutter;
 import emu.grasscutter.server.event.*;
+import emu.grasscutter.utils.JsonUtils;
 import emu.grasscutter.utils.Utils;
 import lombok.*;
 
@@ -82,7 +83,7 @@ public final class PluginManager {
                     InputStreamReader fileReader = new InputStreamReader(configFile.openStream());
 
                     // Create a plugin config instance from the config file.
-                    PluginConfig pluginConfig = Utils.loadJsonToClass(fileReader, PluginConfig.class);
+                    PluginConfig pluginConfig = JsonUtils.loadToClass(fileReader, PluginConfig.class);
                     // Check if the plugin config is valid.
                     if (!pluginConfig.validate()) {
                         Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid config file.");
diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
index a5545877..c3424bc8 100644
--- a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
+++ b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
@@ -7,7 +7,7 @@ import emu.grasscutter.auth.OAuthAuthenticator.ClientType;
 import emu.grasscutter.server.http.Router;
 import emu.grasscutter.server.http.objects.*;
 import emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData;
-import emu.grasscutter.utils.Utils;
+import emu.grasscutter.utils.JsonUtils;
 import express.Express;
 import express.http.Request;
 import express.http.Response;
@@ -53,7 +53,7 @@ public final class DispatchHandler implements Router {
     private static void clientLogin(Request request, Response response) {
         // Parse body data.
         String rawBodyData = request.ctx().body();
-        var bodyData = Utils.jsonDecode(rawBodyData, LoginAccountRequestJson.class);
+        var bodyData = JsonUtils.decode(rawBodyData, LoginAccountRequestJson.class);
         
         // Validate body data.
         if(bodyData == null)
@@ -76,7 +76,7 @@ public final class DispatchHandler implements Router {
     private static void tokenLogin(Request request, Response response) {
         // Parse body data.
         String rawBodyData = request.ctx().body();
-        var bodyData = Utils.jsonDecode(rawBodyData, LoginTokenRequestJson.class);
+        var bodyData = JsonUtils.decode(rawBodyData, LoginTokenRequestJson.class);
 
         // Validate body data.
         if(bodyData == null)
@@ -99,14 +99,14 @@ public final class DispatchHandler implements Router {
     private static void sessionKeyLogin(Request request, Response response) {
         // Parse body data.
         String rawBodyData = request.ctx().body();
-        var bodyData = Utils.jsonDecode(rawBodyData, ComboTokenReqJson.class);
+        var bodyData = JsonUtils.decode(rawBodyData, ComboTokenReqJson.class);
 
         // Validate body data.
         if(bodyData == null || bodyData.data == null)
             return;
         
         // Decode additional body data.
-        var tokenData = Utils.jsonDecode(bodyData.data, LoginTokenData.class);
+        var tokenData = JsonUtils.decode(bodyData.data, LoginTokenData.class);
 
         // Pass data to authentication handler.
         var responseData = Grasscutter.getAuthenticationSystem()
diff --git a/src/main/java/emu/grasscutter/utils/JsonUtils.java b/src/main/java/emu/grasscutter/utils/JsonUtils.java
new file mode 100644
index 00000000..ea2b3483
--- /dev/null
+++ b/src/main/java/emu/grasscutter/utils/JsonUtils.java
@@ -0,0 +1,77 @@
+package emu.grasscutter.utils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+
+public final class JsonUtils {
+    static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+    @Deprecated(forRemoval = true)
+    public static Gson getGsonFactory() {
+        return gson;
+    }
+
+    /*
+     * Encode an object to a JSON string
+     */
+    public static String encode(Object object) {
+        return gson.toJson(object);
+    }
+
+    public static <T> T decode(JsonElement jsonElement, Class<T> classType) throws JsonSyntaxException {
+        return gson.fromJson(jsonElement, classType);
+    }
+
+    public static <T> T loadToClass(InputStreamReader fileReader, Class<T> classType) throws IOException {
+        return gson.fromJson(fileReader, classType);
+    }
+
+    public static <T> T loadToClass(String filename, Class<T> classType) throws IOException {
+        try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
+            return loadToClass(fileReader, classType);
+        }
+    }
+
+    public static <T> List<T> loadToList(InputStreamReader fileReader, Class<T> classType) throws IOException {
+        return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType());
+    }
+
+    public static <T> List<T> loadToList(String filename, Class<T> classType) throws IOException {
+        try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
+            return loadToList(fileReader, classType);
+        }
+    }
+
+    public static <T1,T2> Map<T1,T2> loadToMap(InputStreamReader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException {
+        return gson.fromJson(fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType());
+    }
+
+    public static <T1,T2> Map<T1,T2> loadToMap(String filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
+        try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
+            return loadToMap(fileReader, keyType, valueType);
+        }
+    }
+
+    /**
+     * Safely JSON decodes a given string.
+     * @param jsonData The JSON-encoded data.
+     * @return JSON decoded data, or null if an exception occurred.
+     */
+    public static <T> T decode(String jsonData, Class<T> classType) {
+        try {
+            return gson.fromJson(jsonData, classType);
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java
index 6249739d..5e0d22d3 100644
--- a/src/main/java/emu/grasscutter/utils/Language.java
+++ b/src/main/java/emu/grasscutter/utils/Language.java
@@ -130,7 +130,7 @@ public final class Language {
         languageCode = description.getLanguageCode();
 
         try {
-            languageData = Utils.jsonDecode(Utils.readFromInputStream(description.getLanguageFile()), JsonObject.class);
+            languageData = JsonUtils.decode(Utils.readFromInputStream(description.getLanguageFile()), JsonObject.class);
         } catch (Exception exception) {
             Grasscutter.getLogger().warn("Failed to load language file: " + description.getLanguageCode(), exception);
         }
diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java
index 98d66e7b..bc09a6e6 100644
--- a/src/main/java/emu/grasscutter/utils/Utils.java
+++ b/src/main/java/emu/grasscutter/utils/Utils.java
@@ -20,25 +20,12 @@ import it.unimi.dsi.fastutil.ints.IntList;
 
 import org.slf4j.Logger;
 
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.reflect.TypeToken;
-
 import javax.annotation.Nullable;
 
 import static emu.grasscutter.utils.Language.translate;
 
 @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})
 public final class Utils {
-    private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
-
-    @Deprecated(forRemoval = true)
-    public static Gson getGsonFactory() {
-        return gson;
-    }
-
     public static final Random random = new Random();
 
     public static int randomRange(int min, int max) {
@@ -171,7 +158,7 @@ public final class Utils {
      * @param object The object to log.
      */
     public static void logObject(Object object) {
-        Grasscutter.getLogger().info(jsonEncode(object));
+        Grasscutter.getLogger().info(JsonUtils.encode(object));
     }
 
     /**
@@ -369,57 +356,6 @@ public final class Utils {
         return Base64.getDecoder().decode(toDecode);
     }
 
-    /*
-     * Encode an object to a JSON string
-     */
-    public static String jsonEncode(Object object) {
-        return gson.toJson(object);
-    }
-
-    public static <T> T jsonDecode(JsonElement jsonElement, Class<T> classType) throws JsonSyntaxException {
-        return gson.fromJson(jsonElement, classType);
-    }
-
-    public static <T> T loadJsonToClass(InputStreamReader fileReader, Class<T> classType) throws IOException {
-        return gson.fromJson(fileReader, classType);
-    }
-    public static <T> T loadJsonToClass(String filename, Class<T> classType) throws IOException {
-        try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
-            return loadJsonToClass(fileReader, classType);
-        }
-    }
-
-    public static <T> List<T> loadJsonToList(InputStreamReader fileReader, Class<T> classType) throws IOException {
-        return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType());
-    }
-    public static <T> List<T> loadJsonToList(String filename, Class<T> classType) throws IOException {
-        try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
-            return loadJsonToList(fileReader, classType);
-        }
-    }
-
-    public static <T1,T2> Map<T1,T2> loadJsonToMap(InputStreamReader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException {
-        return gson.fromJson(fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType());
-    }
-    public static <T1,T2> Map<T1,T2> loadJsonToMap(String filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
-        try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
-            return loadJsonToMap(fileReader, keyType, valueType);
-        }
-    }
-
-    /**
-     * Safely JSON decodes a given string.
-     * @param jsonData The JSON-encoded data.
-     * @return JSON decoded data, or null if an exception occurred.
-     */
-    public static <T> T jsonDecode(String jsonData, Class<T> classType) {
-        try {
-            return gson.fromJson(jsonData, classType);
-        } catch (Exception ignored) {
-            return null;
-        }
-    }
-
     /***
      * Draws a random element from the given list, following the given probability distribution, if given.
      * @param list The list from which to draw the element.
-- 
GitLab