Skip to content

Bundled Json in "Json event viewer" #2610

Open

Description

@Sebastianv-tih

When opening the Json event viewer on the new ui, the json is bundled as opposed to the old UI where it is prettified.
Can somebody please look into this?

Old ui

Image

New ui

Image

Activity

  1. niemyjski commented on Oct 6, 2026

    @niemyjski
    Member

    Thanks for reporting this. Could you clarify what you mean by “bundled” and how you expect the JSON to be displayed? Do both screenshots show the same event? Please also share your Exceptionless version and browser/version.

  2. Sebastianv-tih commented on Oct 7, 2026

    @Sebastianv-tih
    Author

    @niemyjski "bundled" meaning it's not "prettified", the object is spanning across one line (look at the Request property on both screenshots and the json structure/object).
    Do you want the current Exceptionless version or the previous one?
    I used Microsoft edge but also tested this out on Safari

    Previous exceptionless was version 6.1.0.0
    Current is 8.8.3

  3. self-assigned this
    on Oct 14, 2026
  4. niemyjski commented on Oct 14, 2026

    @niemyjski
    Member

    Thanks for clarifying. The copied JSON reflects the event returned by the server. In the newer screenshot, the custom data.Request value is a string containing serialized JSON, so its contents are escaped inside the surrounding JSON. This is separate from the known request field, data["@request"].

    Older server versions automatically converted custom data strings containing JSON objects or arrays into structured values. That conversion changed during the System.Text.Json migration in #2135, first released in v8.6.0-rc1. Custom strings now remain strings, which explains why Request is no longer indented as an object.

    I reproduced that server-side difference with the same input payload and compared the tagged Angular implementation with the current Svelte implementation. Both produce equivalent JSON when given the same server response; the difference comes from the value's type before the UI formats it.

    The current Copy JSON action preserves the event's actual value types. Automatically detecting and expanding JSON inside strings would change the copied data. My recommendation is to retain one Copy JSON action with the current behavior, without an expanded-copy option.

    cc @ejsmith

  5. ejsmith commented on Oct 14, 2026

    @ejsmith
    Member

    We should definitely format the JSON data values nicely like we were doing before in the UI. I'd say even for the copy JSON button it's not very useful to have the escaped JSON in the data values.

  6. niemyjski commented on Oct 15, 2026

    @niemyjski
    Member

    @ejsmith, I would revise my earlier recommendation. I propose server-side expansion of custom JSON strings, with controls that preserve their values through storage and later reads. Keep one Copy JSON button.

    This changes API values from strings to objects or arrays. It cannot preserve the original string type. The prototype also has an unresolved root-property conversion issue. We must correct that issue and complete the integration tests before release.

    This concerns custom data.Request, not the known data["@request"] field.

    Technical evidence, alternatives, and required tests

    What changed

    The .NET SDK can send custom objects as JSON strings. Older server versions expanded these strings into objects or arrays.

    The System.Text.Json migration in #2135 removed that general conversion. This explains the different output. Angular and Svelte produce equivalent JSON when they receive equivalent values.

    Expansion restores the earlier server convention. It is not a general rule that every JSON-looking string represents an object. A user can intentionally submit JSON as text.

    What “round trip” means here

    There are two separate requirements:

    1. Preserve the original submitted value.
    2. Preserve the expanded value through later reads and writes.

    Automatic expansion cannot meet the first requirement. A string becomes an object or array. Formatting and escape sequences can also change.

    The second requirement is achievable. The tests must prove it across ingestion, storage, cache serialization, API output, copying, and resubmission.

    The ingestion service saves the submitted payload before parsing. However, permanent retention depends on archive settings. We cannot assume that the original payload will always be available.

    Failures reproduced

    Test Result without the required controls
    Convert {"ID":1,"id":2} through the current inferred dictionary converter The keys merge. One value is lost.
    Read 0.1234567890123456789 through the repository's numeric inference The value becomes 0.12345678901234568.
    Read an embedded date string through type inference Its representation can change from Z to +00:00.
    Expand {"number":1e400} through numeric inference Serialization can fail or produce invalid output.
    Expand {"a":1,"a":2} and store it in Elasticsearch Elasticsearch rejects the document for a duplicate field.
    Expand a deeply nested value It can parse alone, then exceed the depth limit inside an event.
    Copy expanded numbers through JavaScript parse/stringify 9007199254740993 becomes 9007199254740992; 1e400 becomes null.

    Changing ingestion alone does not solve these problems. The repository can convert the object again when it reads the stored document.

    Option A: Expand on the server

    Recommended approach: Preserve custom objects and arrays as JsonElement values at the event deserialization boundary.

    Use the same rules for ingestion, repository reads, and cache reads. Do not change the global object converter for unrelated models.

    Implementation:

    1. Inspect immediate custom event-data values.
    2. For strings that start with { or [, parse strict JSON.
    3. Use JsonDocument and clone the root JsonElement.
    4. Preserve custom object and array tokens as JsonElement when reading them again.
    5. Keep malformed, duplicate-property, and excessive-depth values as strings.
    6. Leave scalar strings and reserved keys unchanged.
    7. Do not recursively expand strings inside the resulting object.
    8. Apply the same preservation rule when unknown root properties move into data.

    System.Text.Json supplies the parser and duplicate-property check. No new dependency or JSONC parser is required.

    A pipeline-only action would affect new events but would not normalize existing stored strings. A write-only serializer change would not protect subsequent reads. Both approaches require additional read handling.

    Benefits:

    • New stored events and API responses use the same structured values.
    • Existing string values can expand on read without a backfill.
    • All API consumers receive the restored representation.
    • Repeated escaping is reduced.

    Costs and compatibility:

    • API consumers that expect strings will receive objects or arrays.
    • Expansion adds parsing and allocation during ingestion.
    • Existing documents change representation if they are later saved.
    • Reverting the code does not restore the original strings.
    • Older readers can still apply the lossy dictionary conversion. Deployment must update readers before enabling expanded writes.

    The current prototype still loses case-distinct keys for an object submitted at the event root. The root-property merge uses the existing dictionary converter. This must be corrected before release.

    Option B: Expand in the client

    Keep ingestion, storage, and API values unchanged. Produce the expanded representation only for the JSON dialog and its single Copy JSON action.

    There are two approaches:

    • Native parse/stringify: Small implementation, but it changes some numbers and discards duplicate properties. It does not meet a value-preservation requirement.
    • JSON text formatter: Read the response as text. Preserve number tokens and existing string tokens. Expand eligible custom-data string values without converting their numbers to JavaScript numbers.

    The existing fetch client supports .text(). A focused prototype passed 11 cases, including large numbers, decimal values, duplicate-property fallback, and repeated formatting.

    No dependency is required. However, this approach introduces a JSON text scanner that we must maintain and test. It must handle escaped quotes, Unicode escapes, duplicate keys, nesting limits, and malformed embedded JSON.

    Benefits:

    • Storage and API contracts remain unchanged.
    • Ingestion has no additional parsing cost.
    • The change is easier to reverse.

    Costs:

    • Displayed and copied values differ from the API's value types.
    • Other API consumers retain the escaped strings.
    • The formatter runs in the browser.
    • A correct implementation is more than a JSON.parse/JSON.stringify helper.

    Copy JSON under either option

    Do not construct the copied text from JavaScript objects if we require numeric preservation.

    For server expansion, read the serialized API response as text. Use a token-preserving formatter only to add whitespace. Use the same text for the JSON dialog and clipboard.

    For client expansion, the formatter must also identify and expand eligible custom-data strings.

    Keep one Copy JSON button. Neither option requires a logging integration.

    Measurements and verification

    A local synthetic test used a 12.7 KB embedded object:

    Measurement Current strings Server expansion
    Ingestion parsing per event 0.056 ms 0.203 ms
    Ingestion allocations 26.7 KB 58.5 KB
    Serialized event size 20.9 KB 12.8 KB

    These are prototype measurements. They are not production throughput or compressed storage measurements.

    Completed checks:

    • A real local Elasticsearch round trip preserved the guarded values.
    • Duplicate-property, malformed, scalar, and excessive-depth inputs remained strings.
    • An existing stored string expanded on read without rewriting its document.
    • Fourteen samples remained stable through five serialization and resubmission cycles.
    • Those cycles included repository serialization, cache serialization, API output, and single-event and batch parsing.
    • Selected stacking, privacy, truncation, typed-data access, and webhook checks passed.
    • Scalar search passed. Custom nested fields remained unindexed.

    These checks do not cover the complete processing pipeline.

    Release requirements

    Before shipping server expansion:

    1. Correct and test root-property conversion.
    2. Test stored strings and stored objects through the full repository and API paths.
    3. Test repeated reads, updates, cache operations, retries, and resubmission.
    4. Test depth limits with the complete response and batch structures.
    5. Verify exact copied number tokens in supported browsers.
    6. Verify reserved-field processing and scalar search.
    7. Review the API type change and mixed-version deployment behavior.
    8. Measure representative workloads before assigning a production performance cost.

    I favor Option A if we accept restoration of the server/API conversion rule. If the API must continue to preserve strings, Option B is the compatible choice.

    Neither option should be described as preserving the original submitted payload after expansion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions