Skip to content

Commit 314245b

Browse files
zhaozhiwenclaude
andcommitted
JSON streamer: emit valid, correctly-nested event objects
Two coupled defects in the JSON event serialization, both fixed here by reworking the shared open/close logic in endEventImpl: - #116: startEventImpl opened the "header" object but nothing closed it, so "generated" and "detectors" were nested inside "header" instead of being siblings. Close the header right after its fields are written (and in the endEvent fallback when the header step is skipped), and drop the now-redundant header-closing brace from endEventImpl. - #115: digitized data was written inline and the "digitized_by_detector" object was closed on every per-detector call. Because the base class interleaves publishEventTrueInfoData/publishEventDigitizedData per detector, a second digitized detector reopened/extended an already closed object, producing invalid JSON (extra braces, misplaced keys). Buffer each detector's digitized array and emit them together as one well-formed "digitized_by_detector" object in endEventImpl. The existing schema is preserved (per-detector "digitized": [] placeholder plus the "digitized_by_detector" map). Validated by running the gstreamer JSON example extended to two detectors: output now parses as valid JSON with header/generated/detectors as siblings and both detectors' digitized hits present (Geant4 11.4.1 / Qt6 dev container). Fixes #115 Fixes #116 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 48f83e5 commit 314245b

4 files changed

Lines changed: 49 additions & 55 deletions

File tree

gemc/gstreamer/factories/JSON/event/event.cc

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ bool GstreamerJsonFactory::startEventImpl(const std::shared_ptr<GEventDataCollec
2929
current_event_has_header = false;
3030
current_event_has_any_detector = false;
3131
current_event_has_generated = false;
32+
current_event_digitized_entries.clear();
3233

3334
// Cache the event number early so it is available to the root event object.
3435
event_number = event_data->getHeader()->getG4LocalEvn();
@@ -47,24 +48,37 @@ bool GstreamerJsonFactory::endEventImpl(const std::shared_ptr<GEventDataCollecti
4748
return false;
4849
}
4950

50-
// If the caller skipped the header step, emit a minimal fallback so the JSON remains valid.
51+
// If the caller skipped the header step, emit a minimal fallback and close the header
52+
// object (startEventImpl opened it; publishEventHeaderImpl normally writes its fields
53+
// and closes it).
5154
if (!current_event_has_header) {
52-
current_event << "\"timestamp\": \"\", \"thread_id\": -1";
55+
current_event << "\"timestamp\": \"\", \"thread_id\": -1}";
5356
}
5457

55-
// Detector publishers leave the "detectors" object open so digitized data
56-
// can be appended after true-information banks without rewriting strings.
57-
if (current_event_has_any_detector) {
58-
current_event << "}";
58+
// Ensure a "detectors" object exists so the schema stays predictable and any buffered
59+
// digitized data has a place to live, even when no true-information banks were published.
60+
bool detectors_has_content = current_event_has_any_detector;
61+
if (!current_event_has_any_detector) {
62+
current_event << ", \"detectors\": {";
63+
current_event_has_any_detector = true;
5964
}
60-
current_event << "}";
6165

62-
// Keep the event schema predictable even when no detector banks were published.
63-
if (!current_event_has_any_detector) {
64-
current_event << ", \"detectors\": {}";
66+
// Emit all buffered digitized detector arrays as a single, well-formed
67+
// "digitized_by_detector" object nested inside "detectors".
68+
if (!current_event_digitized_entries.empty()) {
69+
if (detectors_has_content) { current_event << ", "; }
70+
current_event << "\"digitized_by_detector\": {";
71+
bool first = true;
72+
for (const auto& entry : current_event_digitized_entries) {
73+
if (!first) { current_event << ", "; }
74+
first = false;
75+
current_event << entry;
76+
}
77+
current_event << "}";
6578
}
6679

67-
current_event << "}";
80+
current_event << "}"; // close "detectors"
81+
current_event << "}"; // close the event object
6882

6983
writeTopLevelEntry(current_event.str());
7084

gemc/gstreamer/factories/JSON/event/eventHeader.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bool GstreamerJsonFactory::publishEventHeaderImpl(const std::unique_ptr<GEventHe
2626
current_event << "\"timestamp\": \"" << jsonEscape(timestamp) << "\""
2727
<< ", \"thread_id\": " << thread_id
2828
<< ", \"g4local_event\": " << gevent_header->getG4LocalEvn();
29+
current_event << "}"; // close the "header" object opened in startEventImpl
2930

3031
current_event_has_header = true;
3132
return true;

gemc/gstreamer/factories/JSON/event/publishDigitized.cc

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,80 +17,54 @@ bool GstreamerJsonFactory::publishEventDigitizedDataImpl(const std::string&
1717
return false;
1818
}
1919

20-
// The current design appends digitized content into a reserved object nested under
21-
// "detectors". This avoids rewriting previously emitted detector JSON.
20+
// Build this detector's digitized array into a standalone entry and buffer it.
21+
// endEventImpl emits all buffered entries together as the "digitized_by_detector"
22+
// object, which keeps the JSON valid regardless of how true-info and digitized
23+
// publish calls interleave across detectors.
2224
if (digitizedData.empty()) return true;
2325

24-
static const char* marker = "\"digitized_by_detector\": {";
25-
const std::string assembled = current_event.str();
26-
27-
const bool has_digitized_container = (assembled.find(marker) != std::string::npos);
28-
29-
if (!has_digitized_container) {
30-
// Ensure the event already has a detectors object before reserving a nested map
31-
// for digitized collections keyed by detector name.
32-
if (!current_event_has_any_detector) {
33-
current_event << ", \"detectors\": {";
34-
current_event_has_any_detector = true;
35-
}
36-
else {
37-
current_event << ", ";
38-
}
39-
40-
current_event << "\"digitized_by_detector\": {";
41-
}
42-
43-
// If the reserved object already contains one detector entry, append a comma
44-
// before adding the next one.
45-
const std::string updated = current_event.str();
46-
if (!updated.empty()) {
47-
char last = updated.back();
48-
if (last != '{') current_event << ", ";
49-
}
50-
51-
current_event << "\"" << jsonEscape(detectorName) << "\": [";
26+
std::ostringstream entry;
27+
entry << "\"" << jsonEscape(detectorName) << "\": [";
5228

5329
bool wrote_first_hit = false;
5430
for (const auto* hit : digitizedData) {
5531
if (!hit) continue;
5632

57-
if (wrote_first_hit) current_event << ", ";
33+
if (wrote_first_hit) entry << ", ";
5834
wrote_first_hit = true;
5935

60-
current_event << "{";
36+
entry << "{";
6137

6238
auto addr = getIdentityString(hit->getIdentity());
6339

64-
current_event << "\"address\": \"" << jsonEscape(addr) << "\"";
40+
entry << "\"address\": \"" << jsonEscape(addr) << "\"";
6541

66-
current_event << ", \"vars\": {";
42+
entry << ", \"vars\": {";
6743

6844
bool wrote_first_var = false;
6945

7046
// Integer observables:
7147
// the argument 0 means "do not include SRO variables".
7248
for (const auto& [name, value] : hit->getIntObservablesMap(0)) {
73-
if (wrote_first_var) current_event << ", ";
49+
if (wrote_first_var) entry << ", ";
7450
wrote_first_var = true;
75-
current_event << "\"" << jsonEscape(name) << "\": " << value;
51+
entry << "\"" << jsonEscape(name) << "\": " << value;
7652
}
7753

7854
// Floating-point observables.
7955
for (const auto& [name, value] : hit->getDblObservablesMap(0)) {
80-
if (wrote_first_var) current_event << ", ";
56+
if (wrote_first_var) entry << ", ";
8157
wrote_first_var = true;
82-
current_event << "\"" << jsonEscape(name) << "\": " << value;
58+
entry << "\"" << jsonEscape(name) << "\": " << value;
8359
}
8460

85-
current_event << "}";
86-
current_event << "}";
61+
entry << "}";
62+
entry << "}";
8763
}
8864

89-
current_event << "]";
65+
entry << "]";
9066

91-
// Close the temporary digitized_by_detector object immediately so the enclosing
92-
// event remains structurally valid after this call.
93-
current_event << "}";
67+
current_event_digitized_entries.push_back(entry.str());
9468

9569
return true;
9670
}

gemc/gstreamer/factories/JSON/gstreamerJSONFactory.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ class GstreamerJsonFactory : public GStreamer {
185185
/// \brief Tracks whether the current event already contains generated-particle banks.
186186
bool current_event_has_generated = false;
187187

188+
/// \brief Buffered digitized detector arrays for the current event. endEventImpl emits
189+
/// them together as the "digitized_by_detector" object, which keeps the JSON valid
190+
/// regardless of how true-info and digitized publish calls interleave across detectors.
191+
std::vector<std::string> current_event_digitized_entries;
192+
188193
/// \brief Tracks whether the plugin is currently assembling a frame object.
189194
bool is_building_frame = false;
190195

0 commit comments

Comments
 (0)