Skip to content

Commit 5ed3d84

Browse files
devhawkCopilot
andauthored
Update JSON handling (#370)
This PR breaks up the JSONUtil [big ball of mud](https://en.wikipedia.org/wiki/Spaghetti_code#Big_ball_of_mud). Instead of a single ObjectMapper instance shared by different parts of the code with different needs, this has no been broken up as needed for different parts of the code: * `JsonUtility` has a static "vanilla" object mapper that is used both by `DBOSPortableSerializer` as well as places in the code where we need generic to/from JSON support (admin server & conductor protocol, authenticatedRoles string array) * `DBOSJavaSerializer` includes an object mapper with a custom type resolver. This enables java type info to be included in the serialzied JSON for accurate deserialization. * 'Conductor` has an object mapper configured for large strings which may be recived as import requests Additionally, the custom type resolver approach in `DBOSJavaSerializer` eliminates the need for boxing or wrapping values in array as `JSONUtil` did. This allowed us to remove the `noHistoricalWrapper` parameter from DBOSSerializer methods. Since this is a breaking change for DBOSSerializer (and because there are other breaking changes in the next DBOS Java release) we also changed the method names from javascripty `stringify` & `parse` to `serialize` & `deserialize`. fixes #369 --------- Co-authored-by: Copilot <copilot@github.com>
1 parent 9eea3aa commit 5ed3d84

38 files changed

Lines changed: 806 additions & 839 deletions

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ aspectj = "1.9.22.1"
44
assertj = "3.27.3"
55
cron-utils = "9.2.1"
66
hikaricp = "7.0.2"
7+
kryo = "5.6.2"
78
jackson = "2.21.2"
89
java-websocket = "1.6.0"
910
jspecify = "1.0.0"
@@ -41,6 +42,7 @@ junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
4142
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
4243
junit-pioneer = { module = "org.junit-pioneer:junit-pioneer", version.ref = "junit-pioneer" }
4344
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
45+
kryo = { module = "com.esotericsoftware:kryo", version.ref = "kryo" }
4446
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
4547
maven-artifact = { module = "org.apache.maven:maven-artifact", version.ref = "maven-artifact" }
4648
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }

transact-cli/src/main/java/dev/dbos/transact/cli/WorkflowCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dev.dbos.transact.cli;
22

3-
import dev.dbos.transact.json.JSONUtil.JsonRuntimeException;
3+
import dev.dbos.transact.json.JsonRuntimeException;
44
import dev.dbos.transact.workflow.ForkOptions;
55
import dev.dbos.transact.workflow.ListWorkflowsInput;
66
import dev.dbos.transact.workflow.WorkflowState;

transact/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dependencies {
4646
testImplementation(libs.mockito.core)
4747
testImplementation(libs.sqlite.jdbc)
4848
testImplementation(libs.rest.assured)
49+
testImplementation(libs.kryo)
4950
testImplementation(libs.maven.artifact)
5051
testImplementation(libs.testcontainers.postgresql)
5152
}

transact/src/main/java/dev/dbos/transact/admin/AdminServer.java

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
import dev.dbos.transact.database.SystemDatabase;
44
import dev.dbos.transact.execution.DBOSExecutor;
5+
import dev.dbos.transact.json.JsonUtility;
56
import dev.dbos.transact.workflow.ForkOptions;
67
import dev.dbos.transact.workflow.ListWorkflowsInput;
78
import dev.dbos.transact.workflow.WorkflowHandle;
89

910
import java.io.IOException;
1011
import java.io.OutputStream;
1112
import java.net.InetSocketAddress;
12-
import java.time.Duration;
1313
import java.time.Instant;
1414
import java.util.HashMap;
1515
import java.util.List;
@@ -18,12 +18,7 @@
1818
import java.util.regex.Pattern;
1919
import java.util.stream.Collectors;
2020

21-
import com.fasterxml.jackson.core.JsonGenerator;
2221
import com.fasterxml.jackson.core.type.TypeReference;
23-
import com.fasterxml.jackson.databind.ObjectMapper;
24-
import com.fasterxml.jackson.databind.SerializerProvider;
25-
import com.fasterxml.jackson.databind.module.SimpleModule;
26-
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
2722
import com.sun.net.httpserver.HttpExchange;
2823
import com.sun.net.httpserver.HttpHandler;
2924
import com.sun.net.httpserver.HttpServer;
@@ -32,26 +27,6 @@
3227

3328
public class AdminServer implements AutoCloseable {
3429
private static final Logger logger = LoggerFactory.getLogger(AdminServer.class);
35-
private static final ObjectMapper mapper = new ObjectMapper();
36-
37-
static class DurationSerializer extends StdSerializer<Duration> {
38-
39-
public DurationSerializer() {
40-
super(Duration.class);
41-
}
42-
43-
@Override
44-
public void serialize(Duration value, JsonGenerator gen, SerializerProvider provider)
45-
throws IOException {
46-
gen.writeNumber(value.toMillis() / 1000.0);
47-
}
48-
}
49-
50-
static {
51-
var module = new SimpleModule();
52-
module.addSerializer(Duration.class, new DurationSerializer());
53-
mapper.registerModule(module);
54-
}
5530

5631
private HttpServer server;
5732
private final SystemDatabase systemDatabase;
@@ -145,7 +120,7 @@ private void workflowRecovery(HttpExchange exchange) throws IOException {
145120
if (!ensurePostJson(exchange)) return;
146121

147122
List<String> executorIds =
148-
mapper.readValue(exchange.getRequestBody(), new TypeReference<>() {});
123+
JsonUtility.fromJson(exchange.getRequestBody(), new TypeReference<>() {});
149124
logger.debug("workflowRecovery executors {}", executorIds);
150125
var handles = dbosExecutor.recoverPendingWorkflows(executorIds);
151126
List<String> workflowIds =
@@ -172,7 +147,7 @@ private void workflowQueuesMetadata(HttpExchange exchange) throws IOException {
172147
private void garbageCollect(HttpExchange exchange) throws IOException {
173148
if (!ensurePostJson(exchange)) return;
174149

175-
var request = mapper.readValue(exchange.getRequestBody(), GarbageCollectRequest.class);
150+
var request = JsonUtility.fromJson(exchange.getRequestBody(), GarbageCollectRequest.class);
176151

177152
systemDatabase.garbageCollect(
178153
Instant.ofEpochMilli(request.cutoff_epoch_timestamp_ms), (long) request.rows_threshold);
@@ -183,7 +158,7 @@ private void garbageCollect(HttpExchange exchange) throws IOException {
183158
private void globalTimeout(HttpExchange exchange) throws IOException {
184159
if (!ensurePostJson(exchange)) return;
185160

186-
var request = mapper.readValue(exchange.getRequestBody(), GlobalTimeoutRequest.class);
161+
var request = JsonUtility.fromJson(exchange.getRequestBody(), GlobalTimeoutRequest.class);
187162
dbosExecutor.globalTimeout(Instant.ofEpochMilli(request.cutoff_epoch_timestamp_ms));
188163

189164
exchange.sendResponseHeaders(204, 0);
@@ -192,7 +167,7 @@ private void globalTimeout(HttpExchange exchange) throws IOException {
192167
private void listWorkflows(HttpExchange exchange) throws IOException {
193168
if (!ensurePostJson(exchange)) return;
194169

195-
var request = mapper.readValue(exchange.getRequestBody(), ListWorkflowsRequest.class);
170+
var request = JsonUtility.fromJson(exchange.getRequestBody(), ListWorkflowsRequest.class);
196171
var input = request.asInput();
197172
var workflows = systemDatabase.listWorkflows(input);
198173
var response = workflows.stream().map(WorkflowsOutput::of).collect(Collectors.toList());
@@ -202,7 +177,7 @@ private void listWorkflows(HttpExchange exchange) throws IOException {
202177
private void listQueuedWorkflows(HttpExchange exchange) throws IOException {
203178
if (!ensurePostJson(exchange)) return;
204179

205-
var request = mapper.readValue(exchange.getRequestBody(), ListQueuedWorkflowsRequest.class);
180+
var request = JsonUtility.fromJson(exchange.getRequestBody(), ListQueuedWorkflowsRequest.class);
206181
var input = request.asInput();
207182
var workflows = systemDatabase.listWorkflows(input);
208183
var response = workflows.stream().map(WorkflowsOutput::of).collect(Collectors.toList());
@@ -248,7 +223,7 @@ private void resume(HttpExchange exchange, String wfid) throws IOException {
248223
private void fork(HttpExchange exchange, String wfid) throws IOException {
249224
if (!ensurePostJson(exchange)) return;
250225

251-
var request = mapper.readValue(exchange.getRequestBody(), ForkRequest.class);
226+
var request = JsonUtility.fromJson(exchange.getRequestBody(), ForkRequest.class);
252227
int startStep = request.start_step == null ? 0 : request.start_step;
253228
var options =
254229
new ForkOptions(request.new_workflow_id)
@@ -280,14 +255,10 @@ private static void sendJson(HttpExchange exchange, int statusCode, String json)
280255
}
281256
}
282257

283-
private static void sendMappedJson(HttpExchange exchange, int statusCode, Object json)
258+
private static void sendMappedJson(HttpExchange exchange, int statusCode, Object object)
284259
throws IOException {
285-
exchange.getResponseHeaders().add("Content-Type", "application/json");
286-
byte[] bytes = mapper.writeValueAsBytes(json);
287-
exchange.sendResponseHeaders(statusCode, bytes.length);
288-
try (OutputStream os = exchange.getResponseBody()) {
289-
os.write(bytes);
290-
}
260+
var json = JsonUtility.toJson(object);
261+
sendJson(exchange, statusCode, json);
291262
}
292263

293264
private static boolean ensurePost(HttpExchange exchange) throws IOException {

transact/src/main/java/dev/dbos/transact/admin/StepOutput.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dev.dbos.transact.admin;
22

3-
import dev.dbos.transact.json.JSONUtil;
3+
import dev.dbos.transact.json.JsonUtility;
44
import dev.dbos.transact.workflow.StepInfo;
55

66
/**
@@ -11,8 +11,8 @@ record StepOutput(
1111
int function_id, String function_name, String output, String error, String child_workflow_id) {
1212

1313
static StepOutput of(StepInfo info) {
14-
var output = info.output() == null ? null : JSONUtil.toJson(info.output());
15-
var error = info.error() == null ? null : JSONUtil.toJson(info.error());
14+
var output = info.output() == null ? null : JsonUtility.toJson(info.output());
15+
var error = info.error() == null ? null : JsonUtility.toJson(info.error());
1616
return new StepOutput(
1717
info.functionId(), info.functionName(), output, error, info.childWorkflowId());
1818
}

transact/src/main/java/dev/dbos/transact/admin/WorkflowsOutput.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dev.dbos.transact.admin;
22

3-
import dev.dbos.transact.json.JSONUtil;
3+
import dev.dbos.transact.json.JsonUtility;
44
import dev.dbos.transact.workflow.WorkflowStatus;
55

66
/**
@@ -29,10 +29,12 @@ record WorkflowsOutput(
2929
static WorkflowsOutput of(WorkflowStatus status) {
3030

3131
var roles =
32-
status.authenticatedRoles() == null ? "[]" : JSONUtil.toJson(status.authenticatedRoles());
33-
var input = status.input() == null ? "[]" : JSONUtil.toJson(status.input());
34-
var output = status.output() == null ? null : JSONUtil.toJson(status.output());
35-
var error = status.error() == null ? null : JSONUtil.toJson(status.error());
32+
status.authenticatedRoles() == null
33+
? "[]"
34+
: JsonUtility.toJson(status.authenticatedRoles());
35+
var input = status.input() == null ? "[]" : JsonUtility.toJson(status.input());
36+
var output = status.output() == null ? null : JsonUtility.toJson(status.output());
37+
var error = status.error() == null ? null : JsonUtility.toJson(status.error());
3638

3739
return new WorkflowsOutput(
3840
status.workflowId(),

0 commit comments

Comments
 (0)