Skip to content

Commit f7fc9dc

Browse files
ctruedenclaude
andcommitted
Encapsulate JSON logic a little bit more
Not a *lot* more: ideally, the custom encoder+decoder layer would migrate into the new Json utility class as well in some general form. But for the moment, this gives a simple entry point into JSON encoding+decoding usable outside of the Messages class without needing to import groovy-json classes particularly. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent de9b93a commit f7fc9dc

2 files changed

Lines changed: 91 additions & 20 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*-
2+
* #%L
3+
* Appose: multi-language interprocess cooperation with shared memory.
4+
* %%
5+
* Copyright (C) 2023 - 2026 Appose developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.apposed.appose.util;
31+
32+
import groovy.json.JsonOutput;
33+
import groovy.json.JsonSlurper;
34+
35+
/**
36+
* Utility class for simple JSON serialization and deserialization.
37+
* Encapsulates the Groovy JSON library so that other classes do not
38+
* need to import it directly.
39+
*
40+
* @author Curtis Rueden
41+
*/
42+
public final class Json {
43+
44+
private Json() {
45+
// Prevent instantiation of utility class.
46+
}
47+
48+
/**
49+
* Converts an object to a JSON string.
50+
* Supports standard JSON-compatible types:
51+
* {@link java.util.Map}, {@link java.util.List}, {@link String},
52+
* {@link Number}, {@link Boolean}, and {@code null}.
53+
*
54+
* @param obj The object to serialize.
55+
* @return JSON string representation.
56+
*/
57+
public static String toJson(Object obj) {
58+
return JsonOutput.toJson(obj);
59+
}
60+
61+
/**
62+
* Parses a JSON string into a Java object.
63+
* Returns a {@link java.util.Map} for JSON objects,
64+
* a {@link java.util.List} for JSON arrays, or
65+
* a primitive wrapper for scalar values.
66+
*
67+
* @param json The JSON string to parse.
68+
* @return Parsed Java object.
69+
*/
70+
public static Object parseJson(String json) {
71+
return new JsonSlurper().parseText(json);
72+
}
73+
}

src/main/java/org/apposed/appose/util/Messages.java

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
package org.apposed.appose.util;
3131

3232
import groovy.json.JsonGenerator;
33-
import groovy.json.JsonSlurper;
3433
import org.apposed.appose.NDArray;
3534
import org.apposed.appose.SharedMemory;
3635

@@ -68,9 +67,9 @@ private Messages() {
6867

6968
// Registry of class -> (appose_type, encoder) for encoding custom types.
7069
private static final Map<Class<?>, String> ENCODER_TYPES = new ConcurrentHashMap<>();
71-
private static final Map<Class<?>, Function<Object, Object>> ENCODER_FNS = new ConcurrentHashMap<>();
70+
private static final Map<Class<?>, Function<Object, Object>> ENCODERS = new ConcurrentHashMap<>();
7271

73-
// Registry of appose_type -> factory for decoding custom types.
72+
// Registry of appose_type -> decoder for decoding custom types.
7473
private static final Map<String, Function<Map<String, Object>, Object>> DECODERS =
7574
new ConcurrentHashMap<>();
7675

@@ -83,7 +82,7 @@ private Messages() {
8382
* </p>
8483
* <p>
8584
* When decoding, if a JSON object has the given {@code apposeType}, {@code decoder}
86-
* is called with the {@code "data"} field value and should return the
85+
* is called on the and should return the
8786
* reconstructed object.
8887
* </p>
8988
*
@@ -101,7 +100,7 @@ public static <T> void register(
101100
Function<Map<String, Object>, Object> decoder
102101
) {
103102
ENCODER_TYPES.put(objType, apposeType);
104-
ENCODER_FNS.put(objType, (Function<Object, Object>) (Function<?, ?>) encoder);
103+
ENCODERS.put(objType, (Function<Object, Object>) (Function<?, ?>) encoder);
105104
DECODERS.put(apposeType, decoder);
106105
}
107106

@@ -158,7 +157,7 @@ public static String encode(Map<?, ?> data) {
158157
*/
159158
@SuppressWarnings("unchecked")
160159
public static Map<String, Object> decode(String json) {
161-
return postProcess(new JsonSlurper().parseText(json));
160+
return postProcess(Json.parseJson(json));
162161
}
163162

164163
/**
@@ -174,7 +173,6 @@ public static String stackTrace(Throwable t) {
174173
return sw.toString();
175174
}
176175

177-
178176
// -- Serialization --
179177

180178
/*
@@ -206,22 +204,22 @@ public static String stackTrace(Throwable t) {
206204
*/
207205

208206
/**
209-
* Checks if a type is natively JSON-serializable by Groovy's built-in JSON encoder.
207+
* Checks if a type is natively JSON-serializable by the built-in
208+
* JSON encoder.
210209
* <p>
211210
* These are the basic JSON types that don't need special handling:
212-
* Map, List, String (and other CharSequences), Number, Boolean, and primitives.
213-
* </p>
214-
* <p>
215-
* Other types either implement {@link ForJson} (and are handled by the ForJson
216-
* converter) or will be auto-proxied as worker_object references when in worker mode.
211+
* Map, List, String (and other CharSequences), Number, Boolean, and
212+
* primitives.
217213
* </p>
218214
* <p>
219-
* Note: This list should remain stable. Types implementing {@link ForJson} are
220-
* handled before the catch-all and do not require changes here.
215+
* Other types are handled by a registered encoder if available
216+
* (see {@link #register(Class, String, Function, Function)}),
217+
* or else will be auto-proxied as {@code worker_object} references
218+
* when in worker mode.
221219
* </p>
222220
*
223221
* @param type The class to check
224-
* @return true if this is a basic JSON type that Groovy can serialize natively
222+
* @return true if this is a basic JSON type that can be serialized natively
225223
*/
226224
private static boolean isNativelyJsonSerializable(Class<?> type) {
227225
return Map.class.isAssignableFrom(type)
@@ -241,7 +239,7 @@ public boolean handles(Class<?> type) {
241239

242240
@Override
243241
public Object convert(Object value, String key) {
244-
for (Map.Entry<Class<?>, Function<Object, Object>> entry : ENCODER_FNS.entrySet()) {
242+
for (Map.Entry<Class<?>, Function<Object, Object>> entry : ENCODERS.entrySet()) {
245243
if (entry.getKey().isAssignableFrom(value.getClass())) {
246244
Map<String, Object> map = new LinkedHashMap<>();
247245
map.put("appose_type", ENCODER_TYPES.get(entry.getKey()));
@@ -262,7 +260,7 @@ public boolean handles(Class<?> type) {
262260
// Only active in worker mode.
263261
if (!workerMode) return false;
264262

265-
// Don't auto-proxy types that Groovy's JSON encoder handles natively.
263+
// Don't auto-proxy types that the JSON encoder handles natively.
266264
// Registered types are earlier in the chain and will have already claimed theirs.
267265
return !isNativelyJsonSerializable(type);
268266
}
@@ -306,8 +304,8 @@ private static Object processValue(Object value) {
306304
// by Proxies.proxifyWorkerObjects() in Service.Task.handle().
307305
return map;
308306
}
309-
Function<Map<String, Object>, Object> factory = DECODERS.get(appose_type);
310-
if (factory != null) return factory.apply(map);
307+
Function<Map<String, Object>, Object> decoder = DECODERS.get(appose_type);
308+
if (decoder != null) return decoder.apply(map);
311309
System.err.println("unknown appose_type \"" + appose_type + "\"");
312310
}
313311
return map;

0 commit comments

Comments
 (0)