You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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?
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.
@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
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.
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.
@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:
Preserve the original submitted value.
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
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:
Inspect immediate custom event-data values.
For strings that start with { or [, parse strict JSON.
Use JsonDocument and clone the root JsonElement.
Preserve custom object and array tokens as JsonElement when reading them again.
Keep malformed, duplicate-property, and excessive-depth values as strings.
Leave scalar strings and reserved keys unchanged.
Do not recursively expand strings inside the resulting object.
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:
Correct and test root-property conversion.
Test stored strings and stored objects through the full repository and API paths.
Test repeated reads, updates, cache operations, retries, and resubmission.
Test depth limits with the complete response and batch structures.
Verify exact copied number tokens in supported browsers.
Verify reserved-field processing and scalar search.
Review the API type change and mixed-version deployment behavior.
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.
Activity
niemyjski commentedon Oct 6, 2026
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.
Sebastianv-tih commentedon Oct 7, 2026
@niemyjski "bundled" meaning it's not "prettified", the object is spanning across one line (look at the
Requestproperty 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
niemyjski commentedon Oct 14, 2026
Thanks for clarifying. The copied JSON reflects the event returned by the server. In the newer screenshot, the custom
data.Requestvalue 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
Requestis 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
ejsmith commentedon Oct 14, 2026
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.
niemyjski commentedon Oct 15, 2026
@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 knowndata["@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:
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
{"ID":1,"id":2}through the current inferred dictionary converter0.1234567890123456789through the repository's numeric inference0.12345678901234568.Zto+00:00.{"number":1e400}through numeric inference{"a":1,"a":2}and store it in Elasticsearch9007199254740993becomes9007199254740992;1e400becomesnull.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
JsonElementvalues 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:
{or[, parse strict JSON.JsonDocumentand clone the rootJsonElement.JsonElementwhen reading them again.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:
Costs and compatibility:
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:
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:
Costs:
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:
These are prototype measurements. They are not production throughput or compressed storage measurements.
Completed checks:
These checks do not cover the complete processing pipeline.
Release requirements
Before shipping server expansion:
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.