diff --git a/.junie/plans/consolidate-json-on-jackson.md b/.junie/plans/consolidate-json-on-jackson.md
index e8eafd3..23dee58 100644
--- a/.junie/plans/consolidate-json-on-jackson.md
+++ b/.junie/plans/consolidate-json-on-jackson.md
@@ -217,7 +217,7 @@ graph LR
# Delivery Steps
-### * Step 1: Introduce Jackson and the shared Json utility
+### ✓ Step 1: Introduce Jackson and the shared Json utility
Jackson is an explicit dependency and a single shared `ObjectMapper` is available to the codebase.
- Add `com.fasterxml.jackson.core:jackson-databind` to `pom.xml`, pinned to the version already resolved via JDA (2.21.2), next to the existing `org.json`/`gson` entries (which stay for now so the build keeps compiling).
@@ -225,14 +225,14 @@ Jackson is an explicit dependency and a single shared `ObjectMapper` is availabl
- Have `readObject`/`readArray` wrap Jackson's checked `JsonProcessingException` in an unchecked exception so existing call-site signatures stay unchanged.
- Verify no shade-plugin relocation or filter rules in `pom.xml` reference `org.json` or `gson`.
-### Step 2: Migrate the Gson call sites to Jackson
+### ✓ Step 2: Migrate the Gson call sites to Jackson
Gson is no longer referenced anywhere in the source tree.
- Delete `http/server/GsonJsonMapper.java` and remove its registration from the Javalin configuration, relying on Javalin 7's built-in Jackson `JsonMapper` instead.
- Replace `new Gson().toJson(params)` at `http/WebModule.java:256` with `Json.write(params)`, reusing the shared mapper instead of allocating per request.
- Confirm Javalin request/response serialization still behaves identically for the existing endpoints.
-### Step 3: Migrate the HTTP client and its consumers
+### ✓ Step 3: Migrate the HTTP client and its consumers
`WebModule` returns Jackson nodes and its callers read them.
- Change `WebModule.getAsObject()` to return `ObjectNode` and `getAsArray()` to return `ArrayNode` (lines 133-143), parsing through `Json`.
@@ -240,7 +240,7 @@ Gson is no longer referenced anywhere in the source tree.
- Update `command/crypto/CryptoPriceCommand.java` to read the crypto API response via `JsonNode` accessors.
- Remove the now-unused `org.json` imports from these three files.
-### Step 4: Migrate the task persistence layer
+### ✓ Step 4: Migrate the task persistence layer
Tasks serialize and restore through Jackson while keeping the existing on-disk payload shape.
- Change `PersistableTask.restore(JSONObject)` to `restore(ObjectNode)`.
@@ -249,7 +249,7 @@ Tasks serialize and restore through Jackson while keeping the existing on-disk p
- Update `PersistableTaskRegistry` and `db/service/TaskService` to parse stored payload strings via `Json.readObject`.
- Confirm `tasks/reaction/EmojiReactionRoleTask` still compiles unchanged against the new `EmojiTask` contract.
-### Step 5: Update tests and drop the old dependencies
+### ✓ Step 5: Update tests and drop the old dependencies
`org.json` and Gson are removed from `pom.xml` and the full build passes.
- Rewrite payload construction in `PersistableTaskRegistryTest` and `EmojiReactionRoleTaskPersistenceTest` using `ObjectNode`.
diff --git a/pom.xml b/pom.xml
index a4e0c8e..f7d1422 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,14 +217,9 @@
- org.json
- json
- 20250517
-
-
- com.google.code.gson
- gson
- 2.14.0
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.21.2
diff --git a/src/main/java/me/bobert/netneutrality/bootstrap/BotState.java b/src/main/java/me/bobert/netneutrality/bootstrap/BotState.java
index 873d59d..81840ea 100644
--- a/src/main/java/me/bobert/netneutrality/bootstrap/BotState.java
+++ b/src/main/java/me/bobert/netneutrality/bootstrap/BotState.java
@@ -5,13 +5,14 @@ package me.bobert.netneutrality.bootstrap;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.db.service.GuildServiceProvider;
import me.bobert.netneutrality.db.service.GuildService;
import me.bobert.netneutrality.music.MusicService;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.TrackScheduler;
-import org.json.JSONArray;
-import org.json.JSONObject;
+import me.bobert.netneutrality.utils.Json;
import java.io.File;
import java.io.FileWriter;
@@ -43,8 +44,8 @@ public class BotState {
Map services = guildServices.getServices();
- JSONObject main = new JSONObject();
- JSONArray guildsArray = new JSONArray();
+ ObjectNode main = Json.object();
+ ArrayNode guildsArray = Json.array();
for (MusicManager manager : musicService.getManagers()) {
TrackScheduler trackScheduler = manager.getScheduler();
@@ -53,8 +54,8 @@ public class BotState {
for (GuildService service : services.values()) {
- JSONObject guild = new JSONObject();
- JSONObject music = new JSONObject();
+ ObjectNode guild = Json.object();
+ ObjectNode music = Json.object();
guild.put("id", service.getEntity().getGuildId());
// MessageChannel currentChannel = trackScheduler.getTextChannel();
@@ -64,12 +65,12 @@ public class BotState {
// music.put("current_channel", currentChannel.getId());
// }
- guild.put("music", music);
+ guild.set("music", music);
- guildsArray.put(guild);
+ guildsArray.add(guild);
}
- main.put("guilds", guildsArray);
+ main.set("guilds", guildsArray);
File state = new File("state.json");
try {
diff --git a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java
index 24f9ee2..2597d63 100644
--- a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java
+++ b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java
@@ -5,6 +5,8 @@ package me.bobert.netneutrality.command.crypto;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.command.CommandCategory;
import me.bobert.netneutrality.core.command.CommandExecutionResult;
@@ -15,7 +17,6 @@ import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.http.WebModule;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
-import org.json.JSONObject;
import java.text.NumberFormat;
import java.util.Currency;
@@ -91,14 +92,19 @@ public class CryptoPriceCommand extends AbstractCommand {
}
WebModule http = new WebModule.Builder(String.format(baseURL, symbol, finalCurrency)).cache(30, TimeUnit.MINUTES).build().request();
- JSONObject json = http.getAsObject();
+ ObjectNode json = http.getAsObject();
- if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) {
+ if (json.isEmpty() || json.path("Response").asText("").equalsIgnoreCase("Error")) {
asyncMessage.edit("Crypto doesn't exist.. yet");
return;
}
- double value = json.getDouble(finalCurrency);
+ JsonNode priceNode = json.path(finalCurrency);
+ if (!priceNode.isNumber()) {
+ throw new IllegalStateException("Missing price for currency " + finalCurrency);
+ }
+
+ double value = priceNode.asDouble();
String price = dollarFormat.format(value);
asyncMessage.edit(String.format("The current price of %s in %s is %s", symbol, finalCurrency, price));
@@ -115,12 +121,12 @@ public class CryptoPriceCommand extends AbstractCommand {
private boolean validateSymbol(String symbol) {
try {
WebModule http = new WebModule.Builder(exchangeApi).get().cache(1, TimeUnit.DAYS).build().request();
- JSONObject json = http.getAsObject();
+ ObjectNode json = http.getAsObject();
- JSONObject pairs = json.getJSONObject("Data")
- .getJSONObject("exchanges")
- .getJSONObject(exchange)
- .getJSONObject("pairs");
+ JsonNode pairs = json.path("Data")
+ .path("exchanges")
+ .path(exchange)
+ .path("pairs");
return pairs.has(symbol);
diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java
index 038a5e6..75e87e0 100644
--- a/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java
+++ b/src/main/java/me/bobert/netneutrality/core/tasks/PersistableTask.java
@@ -5,8 +5,8 @@ package me.bobert.netneutrality.core.tasks;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.core.context.CommandContext;
-import org.json.JSONObject;
/**
* Represents a task that can be persisted and restored later.
@@ -29,7 +29,7 @@ public interface PersistableTask extends Task {
*
* @param payload the persisted payload, must not be {@code null}
*/
- void restore(JSONObject payload);
+ void restore(ObjectNode payload);
void insert(CommandContext context);
diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/impl/AbstractPersistableTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/impl/AbstractPersistableTask.java
index f1ef9e0..dfaa45a 100644
--- a/src/main/java/me/bobert/netneutrality/core/tasks/impl/AbstractPersistableTask.java
+++ b/src/main/java/me/bobert/netneutrality/core/tasks/impl/AbstractPersistableTask.java
@@ -5,10 +5,11 @@ package me.bobert.netneutrality.core.tasks.impl;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.tasks.PersistableTask;
import me.bobert.netneutrality.core.tasks.TaskManager;
-import org.json.JSONObject;
+import me.bobert.netneutrality.utils.Json;
public abstract class AbstractPersistableTask extends AbstractTask implements PersistableTask {
@@ -66,15 +67,27 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe
* @param payload the persisted payload
*/
@Override
- public void restore(JSONObject payload) {
+ public void restore(ObjectNode payload) {
// Nothing to restore by default
}
+ /**
+ * No-op default: only tasks carrying extra state need to override this.
+ *
+ * @param payload the payload being written
+ */
+ protected void writePayload(ObjectNode payload) {
+ // Nothing extra to write by default
+ }
+
@Override
public String toPayload() {
- JSONObject payload = new JSONObject(this);
+ ObjectNode payload = Json.object();
payload.put("id", getId());
payload.put("lockId", getLockId());
+ payload.put("name", getName());
+
+ writePayload(payload);
return payload.toString();
}
diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java
index bab1859..8c070f4 100644
--- a/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java
+++ b/src/main/java/me/bobert/netneutrality/core/tasks/impl/EmojiTask.java
@@ -5,9 +5,10 @@ package me.bobert.netneutrality.core.tasks.impl;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.context.tasks.EmojiTaskContext;
import me.bobert.netneutrality.core.tasks.TaskManager;
-import org.json.JSONObject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -27,7 +28,7 @@ public abstract class EmojiTask extends AbstractPersistableTask {
/**
* Constructor used by the persistable task registry: the message coordinates and the emoji
- * mapping are hydrated afterwards by {@link #restore(JSONObject)}.
+ * mapping are hydrated afterwards by {@link #restore(ObjectNode)}.
*
* @param taskManager the application task manager, must not be {@code null}
*/
@@ -36,17 +37,31 @@ public abstract class EmojiTask extends AbstractPersistableTask {
}
@Override
- public void restore(JSONObject payload) {
- this.guildId = payload.optString("guildId", null);
- this.channelId = payload.optString("channelId", null);
- this.messageId = payload.optString("messageId", null);
+ public void restore(ObjectNode payload) {
+ this.guildId = payload.path("guildId").asText(null);
+ this.channelId = payload.path("channelId").asText(null);
+ this.messageId = payload.path("messageId").asText(null);
emojiMapping.clear();
- JSONObject mapping = payload.optJSONObject("emojiMapping");
- if (mapping != null) {
- for (String emoji : mapping.keySet()) {
- emojiMapping.put(emoji, mapping.getString(emoji));
- }
+ JsonNode mapping = payload.path("emojiMapping");
+ if (mapping.isObject()) {
+ mapping.properties().forEach(entry -> emojiMapping.put(entry.getKey(), entry.getValue().asText()));
+ }
+ }
+
+ @Override
+ protected void writePayload(ObjectNode payload) {
+ putIfPresent(payload, "guildId", guildId);
+ putIfPresent(payload, "channelId", channelId);
+ putIfPresent(payload, "messageId", messageId);
+
+ ObjectNode mapping = payload.putObject("emojiMapping");
+ emojiMapping.forEach(mapping::put);
+ }
+
+ private static void putIfPresent(ObjectNode payload, String key, String value) {
+ if (value != null) {
+ payload.put(key, value);
}
}
diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistry.java b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistry.java
index d9e71f6..02a58f3 100644
--- a/src/main/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistry.java
+++ b/src/main/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistry.java
@@ -5,12 +5,12 @@ package me.bobert.netneutrality.core.tasks.persistence;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.bootstrap.ApplicationContext;
import me.bobert.netneutrality.bootstrap.ReflectionHelper;
import me.bobert.netneutrality.core.tasks.PersistableTask;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.json.JSONObject;
import java.util.Collections;
import java.util.Map;
@@ -88,7 +88,7 @@ public final class PersistableTaskRegistry {
* @throws IllegalArgumentException if no task type is registered under {@code typeId}
* @throws ReflectiveOperationException if the task type cannot be instantiated
*/
- public PersistableTask create(String typeId, JSONObject payload) throws ReflectiveOperationException {
+ public PersistableTask create(String typeId, ObjectNode payload) throws ReflectiveOperationException {
Class extends PersistableTask> type = typesById.get(typeId);
if (type == null) {
throw new IllegalArgumentException("No persistable task type registered for id: " + typeId);
diff --git a/src/main/java/me/bobert/netneutrality/db/service/TaskService.java b/src/main/java/me/bobert/netneutrality/db/service/TaskService.java
index b705323..4734426 100644
--- a/src/main/java/me/bobert/netneutrality/db/service/TaskService.java
+++ b/src/main/java/me/bobert/netneutrality/db/service/TaskService.java
@@ -5,14 +5,15 @@ package me.bobert.netneutrality.db.service;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.core.tasks.PersistableTask;
import me.bobert.netneutrality.core.tasks.TaskManager;
import me.bobert.netneutrality.core.tasks.persistence.PersistableTaskRegistry;
import me.bobert.netneutrality.db.entities.OTask;
import me.bobert.netneutrality.db.repository.TaskRepository;
+import me.bobert.netneutrality.utils.Json;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.json.JSONObject;
import java.util.List;
import java.util.Objects;
@@ -42,7 +43,7 @@ public class TaskService {
String payload = oTask.getPayload();
try {
- JSONObject json = new JSONObject(payload);
+ ObjectNode json = Json.readObject(payload);
PersistableTask task = registry.create(typeId, json);
validateTask(task, oTask);
@@ -61,13 +62,13 @@ public class TaskService {
throw new IllegalArgumentException("Task ID mismatch, expected: " + task.getId());
}
- JSONObject json = new JSONObject(payload);
- String payloadId = json.getString("id");
+ ObjectNode json = Json.readObject(payload);
+ String payloadId = json.path("id").asText(null);
if (!Objects.equals(payloadId, task.getId())) {
throw new IllegalArgumentException("Task payload ID mismatch, expected: " + task.getId());
}
- String payloadLockId = json.getString("lockId");
+ String payloadLockId = json.path("lockId").asText(null);
if (!Objects.equals(payloadLockId, task.getLockId())) {
throw new IllegalArgumentException("Task lock ID mismatch, expected: " + task.getLockId());
}
diff --git a/src/main/java/me/bobert/netneutrality/http/WebModule.java b/src/main/java/me/bobert/netneutrality/http/WebModule.java
index 6505694..6042e7a 100644
--- a/src/main/java/me/bobert/netneutrality/http/WebModule.java
+++ b/src/main/java/me/bobert/netneutrality/http/WebModule.java
@@ -6,17 +6,17 @@ package me.bobert.netneutrality.http;
* https://discord.gg/qUa6zNFZsd
*/
-import com.google.gson.Gson;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import me.bobert.netneutrality.http.ResponseCache.CachedResponse;
+import me.bobert.netneutrality.utils.Json;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -130,15 +130,15 @@ public class WebModule {
return keyBuilder.toString();
}
- public JSONObject getAsObject() {
+ public ObjectNode getAsObject() {
return cachedResponse.getBody()
- .map(JSONObject::new)
+ .map(Json::readObject)
.orElseThrow(() -> new IllegalStateException("No body available for this response"));
}
- public JSONArray getAsArray() {
+ public ArrayNode getAsArray() {
return cachedResponse.getBody()
- .map(JSONArray::new)
+ .map(Json::readArray)
.orElseThrow(() -> new IllegalStateException("No body available for this response"));
}
@@ -253,7 +253,7 @@ public class WebModule {
}
if (method == Method.POST && !params.isEmpty()) {
- RequestBody body = RequestBody.create(new Gson().toJson(params), MediaType.parse("application/json; charset=utf-8"));
+ RequestBody body = RequestBody.create(Json.write(params), MediaType.parse("application/json; charset=utf-8"));
builder.post(body);
}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/GsonJsonMapper.java b/src/main/java/me/bobert/netneutrality/http/server/GsonJsonMapper.java
deleted file mode 100644
index cbfa38e..0000000
--- a/src/main/java/me/bobert/netneutrality/http/server/GsonJsonMapper.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package me.bobert.netneutrality.http.server;
-
-/*
- * Copyright (c) anthorob
- *
- * https://discord.gg/qUa6zNFZsd
- */
-
-import com.google.gson.Gson;
-import io.javalin.json.JsonMapper;
-import org.jetbrains.annotations.NotNull;
-
-import java.lang.reflect.Type;
-
-/**
- * Javalin {@link JsonMapper} backed by a shared {@link Gson} instance.
- */
-public class GsonJsonMapper implements JsonMapper {
-
- private static final Gson GSON = new Gson();
-
- @NotNull
- @Override
- public String toJsonString(@NotNull Object obj, @NotNull Type type) {
- return GSON.toJson(obj, type);
- }
-
- @NotNull
- @Override
- public T fromJsonString(@NotNull String json, @NotNull Type targetType) {
- return GSON.fromJson(json, targetType);
- }
-}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/WebApi.java b/src/main/java/me/bobert/netneutrality/http/server/WebApi.java
index 0a3fbf1..06f418f 100644
--- a/src/main/java/me/bobert/netneutrality/http/server/WebApi.java
+++ b/src/main/java/me/bobert/netneutrality/http/server/WebApi.java
@@ -57,7 +57,6 @@ public class WebApi implements Lifecycle {
// Javalin 7 requires routes, exception and error handlers to be registered upfront.
app = Javalin.create(config -> {
- config.jsonMapper(new GsonJsonMapper());
config.startup.showJavalinBanner = false;
config.routes.get("/", ctx -> ctx.html(LIVENESS_HTML));
diff --git a/src/main/java/me/bobert/netneutrality/utils/Json.java b/src/main/java/me/bobert/netneutrality/utils/Json.java
new file mode 100644
index 0000000..961515d
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/Json.java
@@ -0,0 +1,99 @@
+package me.bobert.netneutrality.utils;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Process-wide JSON entry point backed by a single shared {@link ObjectMapper}.
+ */
+public final class Json {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private Json() {
+ }
+
+ public static ObjectMapper mapper() {
+ return MAPPER;
+ }
+
+ public static ObjectNode object() {
+ return MAPPER.createObjectNode();
+ }
+
+ public static ArrayNode array() {
+ return MAPPER.createArrayNode();
+ }
+
+ /**
+ * Parses a JSON document expected to be an object.
+ *
+ * @param json the raw JSON document
+ * @return the parsed object node
+ * @throws JsonException if the document is malformed or is not an object
+ */
+ public static ObjectNode readObject(String json) {
+ JsonNode node = read(json);
+
+ if (!(node instanceof ObjectNode objectNode)) {
+ throw new JsonException("Expected a JSON object but got: " + node.getNodeType());
+ }
+
+ return objectNode;
+ }
+
+ /**
+ * Parses a JSON document expected to be an array.
+ *
+ * @param json the raw JSON document
+ * @return the parsed array node
+ * @throws JsonException if the document is malformed or is not an array
+ */
+ public static ArrayNode readArray(String json) {
+ JsonNode node = read(json);
+
+ if (!(node instanceof ArrayNode arrayNode)) {
+ throw new JsonException("Expected a JSON array but got: " + node.getNodeType());
+ }
+
+ return arrayNode;
+ }
+
+ public static JsonNode read(String json) {
+ try {
+ return MAPPER.readTree(json);
+ } catch (JsonProcessingException e) {
+ throw new JsonException("Cannot parse JSON document", e);
+ }
+ }
+
+ public static String write(Object value) {
+ try {
+ return MAPPER.writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ throw new JsonException("Cannot serialize value to JSON", e);
+ }
+ }
+
+ /**
+ * Unchecked wrapper around Jackson's checked processing exceptions.
+ */
+ public static class JsonException extends RuntimeException {
+ public JsonException(String message) {
+ super(message);
+ }
+
+ public JsonException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/Settings.java b/src/main/java/me/bobert/netneutrality/utils/Settings.java
index b429859..2e33bfb 100644
--- a/src/main/java/me/bobert/netneutrality/utils/Settings.java
+++ b/src/main/java/me/bobert/netneutrality/utils/Settings.java
@@ -6,9 +6,9 @@ package me.bobert.netneutrality.utils;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ArrayNode;
import me.bobert.netneutrality.http.WebModule;
import org.apache.logging.log4j.LogManager;
-import org.json.JSONArray;
import java.io.File;
import java.io.FileInputStream;
@@ -101,11 +101,11 @@ public class Settings implements SettingsProvider {
String gitlabProjectId = "22";
String latestReleaseUrl = String.format("%s/api/v4/projects/%s/releases/", gitlabHost, gitlabProjectId);
- JSONArray json = new WebModule.Builder(latestReleaseUrl).cache(1, TimeUnit.DAYS).build().request().getAsArray();
- if (json.isNull(0)) {
+ ArrayNode json = new WebModule.Builder(latestReleaseUrl).cache(1, TimeUnit.DAYS).build().request().getAsArray();
+ if (json.isEmpty() || !json.get(0).hasNonNull("tag_name")) {
return "0.0.0";
}
- return json.getJSONObject(0).getString("tag_name");
+ return json.get(0).get("tag_name").asText();
}
}
diff --git a/src/test/java/me/bobert/netneutrality/core/tasks/TaskManagerImplTest.java b/src/test/java/me/bobert/netneutrality/core/tasks/TaskManagerImplTest.java
index f905b61..0fcd9c7 100644
--- a/src/test/java/me/bobert/netneutrality/core/tasks/TaskManagerImplTest.java
+++ b/src/test/java/me/bobert/netneutrality/core/tasks/TaskManagerImplTest.java
@@ -214,7 +214,7 @@ class TaskManagerImplTest {
}
@Override
- public void restore(org.json.JSONObject payload) {
+ public void restore(com.fasterxml.jackson.databind.node.ObjectNode payload) {
}
@Override
diff --git a/src/test/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistryTest.java b/src/test/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistryTest.java
index c70e5be..228681a 100644
--- a/src/test/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistryTest.java
+++ b/src/test/java/me/bobert/netneutrality/core/tasks/persistence/PersistableTaskRegistryTest.java
@@ -12,7 +12,7 @@ import me.bobert.netneutrality.core.tasks.TaskManager;
import me.bobert.netneutrality.core.tasks.impl.AbstractPersistableTask;
import me.bobert.netneutrality.core.tasks.impl.EmojiTask;
import me.bobert.netneutrality.tasks.reaction.EmojiReactionRoleTask;
-import org.json.JSONObject;
+import me.bobert.netneutrality.utils.Json;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -49,7 +49,7 @@ class PersistableTaskRegistryTest {
registry.scan();
for (String typeId : registry.getTypeIds()) {
- Class> type = registry.create(typeId, new JSONObject()).getClass();
+ Class> type = registry.create(typeId, Json.object()).getClass();
assertFalse(Modifier.isAbstract(type.getModifiers()), type.getName() + " must not be abstract");
assertNotEquals(AbstractPersistableTask.class, type);
@@ -61,7 +61,7 @@ class PersistableTaskRegistryTest {
void createInstantiatesRegisteredTypeThroughDi() throws Exception {
registry.scan();
- PersistableTask task = registry.create("emoji-reaction-role", new JSONObject());
+ PersistableTask task = registry.create("emoji-reaction-role", Json.object());
assertInstanceOf(EmojiReactionRoleTask.class, task);
}
@@ -70,7 +70,7 @@ class PersistableTaskRegistryTest {
void createRejectsUnknownTypeId() {
registry.scan();
- assertThrows(IllegalArgumentException.class, () -> registry.create("does-not-exist", new JSONObject()));
+ assertThrows(IllegalArgumentException.class, () -> registry.create("does-not-exist", Json.object()));
}
@Test
diff --git a/src/test/java/me/bobert/netneutrality/http/WebModuleTest.java b/src/test/java/me/bobert/netneutrality/http/WebModuleTest.java
index 221eb39..6e23bb5 100644
--- a/src/test/java/me/bobert/netneutrality/http/WebModuleTest.java
+++ b/src/test/java/me/bobert/netneutrality/http/WebModuleTest.java
@@ -45,9 +45,9 @@ class WebModuleTest {
String url = url("cache-hit");
assertEquals(1, new WebModule.Builder(url).get().cache(1, TimeUnit.MINUTES).build().request()
- .getAsObject().getInt("hits"));
+ .getAsObject().get("hits").asInt());
assertEquals(1, new WebModule.Builder(url).get().cache(1, TimeUnit.MINUTES).build().request()
- .getAsObject().getInt("hits"));
+ .getAsObject().get("hits").asInt());
assertEquals(1, hits.get());
}
diff --git a/src/test/java/me/bobert/netneutrality/tasks/reaction/EmojiReactionRoleTaskPersistenceTest.java b/src/test/java/me/bobert/netneutrality/tasks/reaction/EmojiReactionRoleTaskPersistenceTest.java
index 7ae7461..734ca77 100644
--- a/src/test/java/me/bobert/netneutrality/tasks/reaction/EmojiReactionRoleTaskPersistenceTest.java
+++ b/src/test/java/me/bobert/netneutrality/tasks/reaction/EmojiReactionRoleTaskPersistenceTest.java
@@ -5,12 +5,13 @@ package me.bobert.netneutrality.tasks.reaction;
* https://discord.gg/qUa6zNFZsd
*/
+import com.fasterxml.jackson.databind.node.ObjectNode;
import me.bobert.netneutrality.bootstrap.ApplicationContext;
import me.bobert.netneutrality.core.tasks.TaskManager;
import me.bobert.netneutrality.core.tasks.impl.EmojiTask;
import me.bobert.netneutrality.core.tasks.persistence.PersistableTaskRegistry;
import me.bobert.netneutrality.db.service.TaskService;
-import org.json.JSONObject;
+import me.bobert.netneutrality.utils.Json;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -45,7 +46,7 @@ class EmojiReactionRoleTaskPersistenceTest {
Map mapping = Map.of("\uD83D\uDD25", "role-1", "\u2705", "role-2");
EmojiReactionRoleTask original = new EmojiReactionRoleTask(taskManager, "guild-1", "channel-1", "message-1", mapping);
- JSONObject payload = new JSONObject(original.toPayload());
+ ObjectNode payload = Json.readObject(original.toPayload());
EmojiTask restored = assertInstanceOf(EmojiTask.class, registry.create(original.typeId(), payload));
assertEquals(original.getId(), restored.getId());
@@ -68,17 +69,17 @@ class EmojiReactionRoleTaskPersistenceTest {
void payloadAlwaysCarriesIdAndLockId() {
EmojiReactionRoleTask task = new EmojiReactionRoleTask(taskManager, "g", "c", "m");
- JSONObject payload = new JSONObject(task.toPayload());
+ ObjectNode payload = Json.readObject(task.toPayload());
- assertEquals(task.getId(), payload.getString("id"));
- assertEquals(task.getLockId(), payload.getString("lockId"));
+ assertEquals(task.getId(), payload.get("id").asText());
+ assertEquals(task.getLockId(), payload.get("lockId").asText());
}
@Test
void missingEmojiMappingRestoresToEmptyMap() throws Exception {
EmojiReactionRoleTask original = new EmojiReactionRoleTask(taskManager, "guild-1", "channel-1", "message-1");
- JSONObject payload = new JSONObject(original.toPayload());
+ ObjectNode payload = Json.readObject(original.toPayload());
payload.remove("emojiMapping");
EmojiTask restored = assertInstanceOf(EmojiTask.class, registry.create(original.typeId(), payload));
@@ -90,7 +91,7 @@ class EmojiReactionRoleTaskPersistenceTest {
void restoredTaskHasInjectedTaskManager() throws Exception {
EmojiReactionRoleTask original = new EmojiReactionRoleTask(taskManager, "guild-1", "channel-1", "message-1");
- JSONObject payload = new JSONObject(original.toPayload());
+ ObjectNode payload = Json.readObject(original.toPayload());
EmojiTask restored = assertInstanceOf(EmojiTask.class, registry.create(original.typeId(), payload));
restored.addEmoji("\uD83D\uDD25", "role-1");
@@ -98,4 +99,19 @@ class EmojiReactionRoleTaskPersistenceTest {
assertEquals("role-1", restored.getEmojiMapping().get("\uD83D\uDD25"));
verify(taskService).persistTask(restored);
}
+
+ @Test
+ void legacyPayloadStillRestores() throws Exception {
+ String legacy = "{\"id\":\"emoji-reaction-role-guild-1-message-1\",\"lockId\":\"lock-1\",\"name\":\"emoji-reaction-role\","
+ + "\"guildId\":\"guild-1\",\"channelId\":\"channel-1\",\"messageId\":\"message-1\","
+ + "\"emojiMapping\":{\"\uD83D\uDD25\":\"role-1\"}}";
+
+ EmojiTask restored = assertInstanceOf(EmojiTask.class,
+ registry.create("emoji-reaction-role", Json.readObject(legacy)));
+
+ assertEquals("guild-1", restored.getGuildId());
+ assertEquals("channel-1", restored.getChannelId());
+ assertEquals("message-1", restored.getMessageId());
+ assertEquals(Map.of("\uD83D\uDD25", "role-1"), restored.getEmojiMapping());
+ }
}