From 314245b730e60a9de20f6caeba675a26302ef11c Mon Sep 17 00:00:00 2001 From: Zhiwen Zhao Date: Fri, 12 Jun 2026 22:56:50 -0400 Subject: [PATCH] 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 --- gemc/gstreamer/factories/JSON/event/event.cc | 36 +++++++---- .../factories/JSON/event/eventHeader.cc | 1 + .../factories/JSON/event/publishDigitized.cc | 62 ++++++------------- .../factories/JSON/gstreamerJSONFactory.h | 5 ++ 4 files changed, 49 insertions(+), 55 deletions(-) diff --git a/gemc/gstreamer/factories/JSON/event/event.cc b/gemc/gstreamer/factories/JSON/event/event.cc index d0692252..fd5dd4c2 100644 --- a/gemc/gstreamer/factories/JSON/event/event.cc +++ b/gemc/gstreamer/factories/JSON/event/event.cc @@ -29,6 +29,7 @@ bool GstreamerJsonFactory::startEventImpl(const std::shared_ptrgetHeader()->getG4LocalEvn(); @@ -47,24 +48,37 @@ bool GstreamerJsonFactory::endEventImpl(const std::shared_ptrgetG4LocalEvn(); + current_event << "}"; // close the "header" object opened in startEventImpl current_event_has_header = true; return true; diff --git a/gemc/gstreamer/factories/JSON/event/publishDigitized.cc b/gemc/gstreamer/factories/JSON/event/publishDigitized.cc index 578e7a7f..e88c562d 100644 --- a/gemc/gstreamer/factories/JSON/event/publishDigitized.cc +++ b/gemc/gstreamer/factories/JSON/event/publishDigitized.cc @@ -17,80 +17,54 @@ bool GstreamerJsonFactory::publishEventDigitizedDataImpl(const std::string& return false; } - // The current design appends digitized content into a reserved object nested under - // "detectors". This avoids rewriting previously emitted detector JSON. + // Build this detector's digitized array into a standalone entry and buffer it. + // endEventImpl emits all buffered entries together as the "digitized_by_detector" + // object, which keeps the JSON valid regardless of how true-info and digitized + // publish calls interleave across detectors. if (digitizedData.empty()) return true; - static const char* marker = "\"digitized_by_detector\": {"; - const std::string assembled = current_event.str(); - - const bool has_digitized_container = (assembled.find(marker) != std::string::npos); - - if (!has_digitized_container) { - // Ensure the event already has a detectors object before reserving a nested map - // for digitized collections keyed by detector name. - if (!current_event_has_any_detector) { - current_event << ", \"detectors\": {"; - current_event_has_any_detector = true; - } - else { - current_event << ", "; - } - - current_event << "\"digitized_by_detector\": {"; - } - - // If the reserved object already contains one detector entry, append a comma - // before adding the next one. - const std::string updated = current_event.str(); - if (!updated.empty()) { - char last = updated.back(); - if (last != '{') current_event << ", "; - } - - current_event << "\"" << jsonEscape(detectorName) << "\": ["; + std::ostringstream entry; + entry << "\"" << jsonEscape(detectorName) << "\": ["; bool wrote_first_hit = false; for (const auto* hit : digitizedData) { if (!hit) continue; - if (wrote_first_hit) current_event << ", "; + if (wrote_first_hit) entry << ", "; wrote_first_hit = true; - current_event << "{"; + entry << "{"; auto addr = getIdentityString(hit->getIdentity()); - current_event << "\"address\": \"" << jsonEscape(addr) << "\""; + entry << "\"address\": \"" << jsonEscape(addr) << "\""; - current_event << ", \"vars\": {"; + entry << ", \"vars\": {"; bool wrote_first_var = false; // Integer observables: // the argument 0 means "do not include SRO variables". for (const auto& [name, value] : hit->getIntObservablesMap(0)) { - if (wrote_first_var) current_event << ", "; + if (wrote_first_var) entry << ", "; wrote_first_var = true; - current_event << "\"" << jsonEscape(name) << "\": " << value; + entry << "\"" << jsonEscape(name) << "\": " << value; } // Floating-point observables. for (const auto& [name, value] : hit->getDblObservablesMap(0)) { - if (wrote_first_var) current_event << ", "; + if (wrote_first_var) entry << ", "; wrote_first_var = true; - current_event << "\"" << jsonEscape(name) << "\": " << value; + entry << "\"" << jsonEscape(name) << "\": " << value; } - current_event << "}"; - current_event << "}"; + entry << "}"; + entry << "}"; } - current_event << "]"; + entry << "]"; - // Close the temporary digitized_by_detector object immediately so the enclosing - // event remains structurally valid after this call. - current_event << "}"; + current_event_digitized_entries.push_back(entry.str()); return true; } \ No newline at end of file diff --git a/gemc/gstreamer/factories/JSON/gstreamerJSONFactory.h b/gemc/gstreamer/factories/JSON/gstreamerJSONFactory.h index dcc575c4..ce50c047 100644 --- a/gemc/gstreamer/factories/JSON/gstreamerJSONFactory.h +++ b/gemc/gstreamer/factories/JSON/gstreamerJSONFactory.h @@ -185,6 +185,11 @@ class GstreamerJsonFactory : public GStreamer { /// \brief Tracks whether the current event already contains generated-particle banks. bool current_event_has_generated = false; + /// \brief Buffered digitized detector arrays for the current event. endEventImpl emits + /// them together as the "digitized_by_detector" object, which keeps the JSON valid + /// regardless of how true-info and digitized publish calls interleave across detectors. + std::vector current_event_digitized_entries; + /// \brief Tracks whether the plugin is currently assembling a frame object. bool is_building_frame = false;