Skip to content

Commit 2eb7478

Browse files
authored
Merge pull request #493 from avinxshKD/fix/zmq-json-interop
fix(zmq): use JSON on wire, accept JSON keywords in parser
2 parents 142cc7c + 4534bf2 commit 2eb7478

File tree

2 files changed

+102
-5
lines changed

2 files changed

+102
-5
lines changed

TestLiteralEval.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ public static void main(String[] args) {
3737
testUnterminatedDict();
3838
testUnterminatedTuple();
3939
testNonStringDictKey();
40+
testJsonKeywordTrue();
41+
testJsonKeywordFalse();
42+
testJsonKeywordNull();
43+
testJsonMixedList();
44+
testJsonRoundTrip();
4045

4146
System.out.println("\n=== Results: " + passed + " passed, " + failed + " failed out of " + (passed + failed) + " tests ===");
4247
if (failed > 0) {
@@ -267,4 +272,42 @@ static void testNonStringDictKey() {
267272
check("numeric dict key throws", true, e.getMessage().contains("Dict keys must be non-null strings"));
268273
}
269274
}
275+
276+
// --- JSON keyword interop tests ---
277+
278+
static void testJsonKeywordTrue() {
279+
Object result = concoredocker.literalEval("true");
280+
check("json true -> Boolean.TRUE", Boolean.TRUE, result);
281+
}
282+
283+
static void testJsonKeywordFalse() {
284+
Object result = concoredocker.literalEval("false");
285+
check("json false -> Boolean.FALSE", Boolean.FALSE, result);
286+
}
287+
288+
static void testJsonKeywordNull() {
289+
Object result = concoredocker.literalEval("null");
290+
check("json null -> null", null, result);
291+
}
292+
293+
static void testJsonMixedList() {
294+
// Python sends [0.0, true, null] via json.dumps — Java must parse it
295+
@SuppressWarnings("unchecked")
296+
List<Object> result = (List<Object>) concoredocker.literalEval("[0.0, true, null]");
297+
check("json list[0] simtime", 0.0, result.get(0));
298+
check("json list[1] true", Boolean.TRUE, result.get(1));
299+
check("json list[2] null", null, result.get(2));
300+
}
301+
302+
static void testJsonRoundTrip() {
303+
// JSON-style payload: true/false/null and double-quoted strings
304+
String jsonPayload = "[0.0, 1.5, true, false, null, \"hello\"]";
305+
@SuppressWarnings("unchecked")
306+
List<Object> result = (List<Object>) concoredocker.literalEval(jsonPayload);
307+
check("json round-trip double", 1.5, result.get(1));
308+
check("json round-trip true", Boolean.TRUE, result.get(2));
309+
check("json round-trip false", Boolean.FALSE, result.get(3));
310+
check("json round-trip null", null, result.get(4));
311+
check("json round-trip string", "hello", result.get(5));
312+
}
270313
}

concoredocker.java

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,60 @@ private static String toPythonLiteral(Object obj) {
281281
return obj.toString();
282282
}
283283

284+
/**
285+
* Escapes a Java string so it can be safely embedded in a JSON double-quoted string.
286+
* Escapes backslash, double quote, newline, carriage return, and tab.
287+
*/
288+
private static String escapeJsonString(String s) {
289+
StringBuilder sb = new StringBuilder(s.length());
290+
for (int i = 0; i < s.length(); i++) {
291+
char c = s.charAt(i);
292+
switch (c) {
293+
case '\\': sb.append("\\\\"); break;
294+
case '"': sb.append("\\\""); break;
295+
case '\n': sb.append("\\n"); break;
296+
case '\r': sb.append("\\r"); break;
297+
case '\t': sb.append("\\t"); break;
298+
default: sb.append(c); break;
299+
}
300+
}
301+
return sb.toString();
302+
}
303+
304+
/**
305+
* Converts a Java object to its JSON string representation.
306+
* true/false/null instead of True/False/None; strings double-quoted.
307+
*/
308+
private static String toJsonLiteral(Object obj) {
309+
if (obj == null) return "null";
310+
if (obj instanceof Boolean) return ((Boolean) obj) ? "true" : "false";
311+
if (obj instanceof String) return "\"" + escapeJsonString((String) obj) + "\"";
312+
if (obj instanceof Number) return obj.toString();
313+
if (obj instanceof List) {
314+
List<?> list = (List<?>) obj;
315+
StringBuilder sb = new StringBuilder("[");
316+
for (int i = 0; i < list.size(); i++) {
317+
if (i > 0) sb.append(", ");
318+
sb.append(toJsonLiteral(list.get(i)));
319+
}
320+
sb.append("]");
321+
return sb.toString();
322+
}
323+
if (obj instanceof Map) {
324+
Map<?, ?> map = (Map<?, ?>) obj;
325+
StringBuilder sb = new StringBuilder("{");
326+
boolean first = true;
327+
for (Map.Entry<?, ?> entry : map.entrySet()) {
328+
if (!first) sb.append(", ");
329+
sb.append(toJsonLiteral(entry.getKey())).append(": ").append(toJsonLiteral(entry.getValue()));
330+
first = false;
331+
}
332+
sb.append("}");
333+
return sb.toString();
334+
}
335+
return obj.toString();
336+
}
337+
284338
/**
285339
* Writes data to a port file.
286340
* Prepends simtime+delta to the value list, then serializes to Python-literal format.
@@ -437,10 +491,10 @@ public static void write(String portName, String name, Object val, int delta) {
437491
if (val instanceof List) {
438492
List<?> listVal = (List<?>) val;
439493
StringBuilder sb = new StringBuilder("[");
440-
sb.append(toPythonLiteral(simtime + delta));
494+
sb.append(toJsonLiteral(simtime + delta));
441495
for (Object o : listVal) {
442496
sb.append(", ");
443-
sb.append(toPythonLiteral(o));
497+
sb.append(toJsonLiteral(o));
444498
}
445499
sb.append("]");
446500
payload = sb.toString();
@@ -750,9 +804,9 @@ Object parseKeyword() {
750804
}
751805
String word = input.substring(start, pos);
752806
switch (word) {
753-
case "True": return Boolean.TRUE;
754-
case "False": return Boolean.FALSE;
755-
case "None": return null;
807+
case "True": case "true": return Boolean.TRUE;
808+
case "False": case "false": return Boolean.FALSE;
809+
case "None": case "null": return null;
756810
default: throw new IllegalArgumentException("Unknown keyword: '" + word + "' at position " + start);
757811
}
758812
}

0 commit comments

Comments
 (0)