@@ -156,7 +156,7 @@ public void Serialize_Error_IsValidJSON() {
156156 string json = serializer . Serialize ( error ) ;
157157
158158 // Assert
159- Assert . Equal ( "{\" modules\" :[{\" module_id\" :1,\" name\" :\" TestModule\" ,\" version\" :\" 1.0.0\" ,\" is_entry\" :true,\" created_date\" :\" 2023-05-01T12:00:00Z\" ,\" modified_date\" :\" 2023-05-02T12:00:00Z\" ,\" data\" :{\" PublicKeyToken\" :\" b03f5f7f11d50a3a\" }}],\" message\" :\" Test error message\" ,\" type\" :\" System.Exception\" ,\" code\" :\" 1001\" ,\" data\" :{\" @ext\" :{\" OrderNumber\" :10}},\" inner\" :{\" message\" :\" Inner error message\" ,\" type\" :\" System.ArgumentException\" ,\" code\" :\" 2002\" ,\" data\" :{},\" inner\" :null,\" stack_trace\" :[{\" file_name\" :null,\" line_number\" :20,\" column\" :0,\" is_signature_target\" :false,\" declaring_namespace\" :null,\" declaring_type\" :null,\" name\" :\" InnerMethodName\" ,\" module_id\" :0,\" data\" :{},\" generic_arguments\" :[],\" parameters\" :[]}],\" target_method\" :null},\" stack_trace\" :[{\" file_name\" :\" TestFile.cs\" ,\" line_number\" :20,\" column\" :5,\" is_signature_target\" :true,\" declaring_namespace\" :\" TestNamespace\" ,\" declaring_type\" :\" TestClass\" ,\" name\" :\" InnerMethodName\" ,\" module_id\" :1,\" data\" :{\" StackFrameKey\" :\" StackFrameValue\" },\" generic_arguments\" :[\" T\" ],\" parameters\" :[{\" name\" :\" param1\" ,\" type\" :\" System.String\" ,\" type_namespace\" :\" System\" ,\" data\" :{\" ParameterKey\" :\" ParameterValue\" },\" generic_arguments\" :[\" U\" ]}]}],\" target_method\" :null}" , json ) ;
159+ Assert . Equal ( "{\" modules\" :[{\" module_id\" :1,\" name\" :\" TestModule\" ,\" version\" :\" 1.0.0\" ,\" is_entry\" :true,\" created_date\" :\" 2023-05-01T12:00:00Z\" ,\" modified_date\" :\" 2023-05-02T12:00:00Z\" ,\" data\" :{\" PublicKeyToken\" :\" b03f5f7f11d50a3a\" }}],\" message\" :\" Test error message\" ,\" type\" :\" System.Exception\" ,\" code\" :\" 1001\" ,\" data\" :{\" @ext\" :{\" order_number\" :10}},\" inner\" :{\" message\" :\" Inner error message\" ,\" type\" :\" System.ArgumentException\" ,\" code\" :\" 2002\" ,\" data\" :{},\" inner\" :null,\" stack_trace\" :[{\" file_name\" :null,\" line_number\" :20,\" column\" :0,\" is_signature_target\" :false,\" declaring_namespace\" :null,\" declaring_type\" :null,\" name\" :\" InnerMethodName\" ,\" module_id\" :0,\" data\" :{},\" generic_arguments\" :[],\" parameters\" :[]}],\" target_method\" :null},\" stack_trace\" :[{\" file_name\" :\" TestFile.cs\" ,\" line_number\" :20,\" column\" :5,\" is_signature_target\" :true,\" declaring_namespace\" :\" TestNamespace\" ,\" declaring_type\" :\" TestClass\" ,\" name\" :\" InnerMethodName\" ,\" module_id\" :1,\" data\" :{\" StackFrameKey\" :\" StackFrameValue\" },\" generic_arguments\" :[\" T\" ],\" parameters\" :[{\" name\" :\" param1\" ,\" type\" :\" System.String\" ,\" type_namespace\" :\" System\" ,\" data\" :{\" ParameterKey\" :\" ParameterValue\" },\" generic_arguments\" :[\" U\" ]}]}],\" target_method\" :null}" , json ) ;
160160 }
161161
162162 [ Fact ]
@@ -263,7 +263,7 @@ public void Serialize_SimpleError_IsValidJSON() {
263263 string json = serializer . Serialize ( simpleError ) ;
264264
265265 // Assert
266- Assert . Equal ( "{\" modules\" :[{\" module_id\" :1,\" name\" :\" TestModule\" ,\" version\" :\" 1.0.0\" ,\" is_entry\" :true,\" created_date\" :\" 2023-05-01T12:00:00Z\" ,\" modified_date\" :\" 2023-05-02T12:00:00Z\" ,\" data\" :{\" PublicKeyToken\" :\" b77a5c561934e089\" }}],\" message\" :\" Test error message\" ,\" type\" :\" System.Exception\" ,\" stack_trace\" :\" at TestClass.TestMethod()\" ,\" data\" :{\" @ext\" :{\" OrderNumber \" :10}},\" inner\" :{\" message\" :\" Inner error message\" ,\" type\" :\" System.NullReferenceException\" ,\" stack_trace\" :\" at InnerTestClass.InnerTestMethod()\" ,\" data\" :{},\" inner\" :null}}" , json ) ;
266+ Assert . Equal ( "{\" modules\" :[{\" module_id\" :1,\" name\" :\" TestModule\" ,\" version\" :\" 1.0.0\" ,\" is_entry\" :true,\" created_date\" :\" 2023-05-01T12:00:00Z\" ,\" modified_date\" :\" 2023-05-02T12:00:00Z\" ,\" data\" :{\" PublicKeyToken\" :\" b77a5c561934e089\" }}],\" message\" :\" Test error message\" ,\" type\" :\" System.Exception\" ,\" stack_trace\" :\" at TestClass.TestMethod()\" ,\" data\" :{\" @ext\" :{\" order_number \" :10}},\" inner\" :{\" message\" :\" Inner error message\" ,\" type\" :\" System.NullReferenceException\" ,\" stack_trace\" :\" at InnerTestClass.InnerTestMethod()\" ,\" data\" :{},\" inner\" :null}}" , json ) ;
267267 }
268268
269269 [ Fact ]
@@ -337,7 +337,7 @@ public void Serialize_ModelWithExclusions_ShouldExcludeProperties() {
337337 string json = serializer . Serialize ( data , new [ ] { nameof ( SampleModel . Date ) , nameof ( SampleModel . Number ) , nameof ( SampleModel . Rating ) , nameof ( SampleModel . Bool ) , nameof ( SampleModel . DateOffset ) , nameof ( SampleModel . Direction ) , nameof ( SampleModel . Collection ) , nameof ( SampleModel . Dictionary ) , nameof ( SampleModel . Nested ) } ) ;
338338
339339 // Assert
340- Assert . Equal ( "{\" Message \" :\" Testing\" }" , json ) ;
340+ Assert . Equal ( "{\" message \" :\" Testing\" }" , json ) ;
341341 }
342342
343343 [ Fact ]
@@ -358,7 +358,7 @@ public void Serialize_ModelWithNestedExclusions_WillExcludeNestedProperties() {
358358 string json = serializer . Serialize ( data , new [ ] { nameof ( NestedModel . Number ) } ) ;
359359
360360 // Assert
361- Assert . Equal ( "{\" Message \" :\" Testing\" ,\" Nested \" :{\" Message \" :\" Nested\" ,\" Nested \" :null}}" , json ) ;
361+ Assert . Equal ( "{\" message \" :\" Testing\" ,\" nested \" :{\" message \" :\" Nested\" ,\" nested \" :null}}" , json ) ;
362362 }
363363
364364 [ Fact ]
@@ -371,7 +371,7 @@ public void Serialize_ModelWithNullValues_ShouldIncludeNullObjects() {
371371 string json = serializer . Serialize ( data ) ;
372372
373373 // Assert
374- Assert . Equal ( "{\" Number \" :0,\" Bool \" :false,\" Message \" :null,\" Collection \" :null,\" Dictionary \" :null,\" DataDictionary \" :null}" , json ) ;
374+ Assert . Equal ( "{\" number \" :0,\" bool \" :false,\" message \" :null,\" collection \" :null,\" dictionary \" :null,\" data_dictionary \" :null}" , json ) ;
375375 }
376376
377377 [ Fact ]
@@ -396,7 +396,7 @@ public void Serialize_ModelWithComplexPropertyNames_ShouldExcludeMultiWordProper
396396 string json = serializer . Serialize ( user , exclusions , maxDepth : 2 ) ;
397397
398398 // Assert
399- Assert . Equal ( "{\" FirstName \" :\" John\" ,\" LastName \" :\" Doe\" ,\" Billing \" :{\" ExpirationMonth \" :10,\" ExpirationYear \" :2020}}" , json ) ;
399+ Assert . Equal ( "{\" first_name \" :\" John\" ,\" last_name \" :\" Doe\" ,\" billing \" :{\" expiration_month \" :10,\" expiration_year \" :2020}}" , json ) ;
400400 }
401401
402402 [ Fact ]
@@ -409,7 +409,7 @@ public void Serialize_ModelWithDefaultValues_ShouldIncludeDefaultValues() {
409409 string json = serializer . Serialize ( data , new [ ] { nameof ( SampleModel . Date ) , nameof ( SampleModel . DateOffset ) } ) ;
410410
411411 // Assert
412- Assert . Equal ( "{\" Number \" :0,\" Rating \" :0,\" Bool \" :false,\" Direction \" :\" North\" ,\" Message \" :null,\" Dictionary \" :null,\" Collection \" :null,\" Nested \" :null}" , json ) ;
412+ Assert . Equal ( "{\" number \" :0,\" rating \" :0,\" bool \" :false,\" direction \" :\" North\" ,\" message \" :null,\" dictionary \" :null,\" collection \" :null,\" nested \" :null}" , json ) ;
413413
414414 var model = serializer . Deserialize < SampleModel > ( json ) ;
415415 Assert . Equal ( data . Number , model . Number ) ;
@@ -440,7 +440,7 @@ public void Serialize_ModelWithDataTypes_ShouldSerializeValues() {
440440 string json = serializer . Serialize ( data ) ;
441441
442442 // Assert
443- Assert . Equal ( "{\" Number \" :1,\" Rating \" :4.50,\" Bool \" :true,\" Direction \" :\" North\" ,\" Date \" :\" 9999-12-31T23:59:59.9999999\" ,\" Message \" :\" test\" ,\" DateOffset \" :\" 9999-12-31T23:59:59.9999999+00:00\" ,\" Dictionary \" :{\" key\" :\" value\" },\" Collection \" :[\" one\" ],\" Nested \" :null}" , json ) ;
443+ Assert . Equal ( "{\" number \" :1,\" rating \" :4.50,\" bool \" :true,\" direction \" :\" North\" ,\" date \" :\" 9999-12-31T23:59:59.9999999\" ,\" message \" :\" test\" ,\" date_offset \" :\" 9999-12-31T23:59:59.9999999+00:00\" ,\" dictionary \" :{\" key\" :\" value\" },\" collection \" :[\" one\" ],\" nested \" :null}" , json ) ;
444444
445445 var model = serializer . Deserialize < SampleModel > ( json ) ;
446446 Assert . Equal ( data . Number , model . Number ) ;
@@ -469,7 +469,7 @@ public void Serialize_NestedModel_ShouldRespectSetMaxDepth() {
469469 string json = serializer . Serialize ( data , new [ ] { nameof ( NestedModel . Number ) } , maxDepth : 2 ) ;
470470
471471 // Assert
472- Assert . Equal ( "{\" Message \" :\" Level 1\" ,\" Nested \" :{\" Message \" :\" Level 2\" }}" , json ) ;
472+ Assert . Equal ( "{\" message \" :\" Level 1\" ,\" nested \" :{\" message \" :\" Level 2\" }}" , json ) ;
473473 }
474474
475475 [ Fact ]
@@ -486,7 +486,7 @@ public void Serialize_ModelWithNullCollections_ShouldBeSerialized() {
486486 string json = serializer . Serialize ( data , new [ ] { nameof ( DefaultsModel . Message ) , nameof ( DefaultsModel . Bool ) , nameof ( DefaultsModel . Number ) } ) ;
487487
488488 // Assert
489- Assert . Equal ( "{\" Collection \" :null,\" Dictionary \" :null,\" DataDictionary \" :null}" , json ) ;
489+ Assert . Equal ( "{\" collection \" :null,\" dictionary \" :null,\" data_dictionary \" :null}" , json ) ;
490490 }
491491
492492 [ Fact ]
@@ -503,7 +503,7 @@ public void Serialize_ModelWithEmptyCollections_ShouldBeSerialized() {
503503 string json = serializer . Serialize ( data , new [ ] { nameof ( DefaultsModel . Message ) , nameof ( DefaultsModel . Bool ) , nameof ( DefaultsModel . Number ) } ) ;
504504
505505 // Assert
506- Assert . Equal ( "{\" Collection \" :[],\" Dictionary \" :{},\" DataDictionary \" :{}}" , json ) ;
506+ Assert . Equal ( "{\" collection \" :[],\" dictionary \" :{},\" data_dictionary \" :{}}" , json ) ;
507507 }
508508
509509 [ Fact ]
@@ -520,7 +520,7 @@ public void Serialize_ModelWithDictionaryValues_ShouldRespectDictionaryKeyNames(
520520 string json = serializer . Serialize ( data , new [ ] { nameof ( DefaultsModel . Message ) , nameof ( DefaultsModel . Bool ) , nameof ( DefaultsModel . Number ) } ) ;
521521
522522 // Assert
523- Assert . Equal ( "{\" Collection \" :[\" Collection\" ],\" Dictionary \" :{\" ItEm\" :\" Value\" },\" DataDictionary \" :{\" ItEm\" :\" Value\" }}" , json ) ;
523+ Assert . Equal ( "{\" collection \" :[\" Collection\" ],\" dictionary \" :{\" ItEm\" :\" Value\" },\" data_dictionary \" :{\" ItEm\" :\" Value\" }}" , json ) ;
524524 }
525525
526526 [ Fact ]
@@ -575,7 +575,53 @@ public void Serialize_PostDataConverter_ShouldHandleRequestInfoConverterPostData
575575 string json = serializer . Serialize ( requestInfo , propertiesToExclude ) ;
576576
577577 // Assert
578- Assert . Equal ( "{\" post_data\" :{\" Age\" :21}}" , json ) ;
578+ Assert . Equal ( "{\" post_data\" :{\" age\" :21}}" , json ) ;
579+ }
580+
581+ [ Fact ]
582+ public void Serialize_Event_DataDictionaryRoundTrip_PreservesObjectStructure ( ) {
583+ // Regression test: After roundtripping through storage (serialize → deserialize),
584+ // complex objects in Data become JSON strings. When re-serialized for API submission,
585+ // they must be emitted as JSON objects (not escaped strings).
586+ var serializer = GetSerializer ( ) ;
587+
588+ // Simulate what plugins do: store an object directly in Data
589+ var ev = new Event {
590+ Type = Event . KnownTypes . Error ,
591+ Data = {
592+ [ Event . KnownDataKeys . Error ] = new Error {
593+ Message = "Test error" ,
594+ Type = "System.Exception"
595+ } ,
596+ [ Event . KnownDataKeys . EnvironmentInfo ] = new EnvironmentInfo {
597+ ProcessorCount = 8 ,
598+ OSName = "Windows" ,
599+ OSVersion = "10.0"
600+ }
601+ }
602+ } ;
603+
604+ // First serialize (to storage) - uses Serialize<T> stream path via string overload
605+ string storageJson = serializer . Serialize ( ev ) ;
606+
607+ // Verify first serialization produces JSON objects for data values
608+ Assert . Contains ( "\" @error\" :" , storageJson ) ;
609+ Assert . DoesNotContain ( "\" @error\" :\" " , storageJson ) ; // should NOT be a string
610+
611+ // Roundtrip through storage (deserialize then re-serialize, simulating queue)
612+ var deserialized = ( Event ) serializer . Deserialize ( storageJson , typeof ( Event ) ) ;
613+
614+ // After deserialization, complex Data values become strings (DataDictionaryConverter behavior)
615+ Assert . IsType < string > ( deserialized . Data [ Event . KnownDataKeys . Error ] ) ;
616+ Assert . IsType < string > ( deserialized . Data [ Event . KnownDataKeys . EnvironmentInfo ] ) ;
617+
618+ // Re-serialize for API submission - JSON strings must be emitted as raw JSON objects
619+ string apiJson = serializer . Serialize ( deserialized ) ;
620+ Assert . DoesNotContain ( "\" @error\" :\" " , apiJson ) ; // Must NOT be an escaped string
621+ Assert . DoesNotContain ( "\" @environment\" :\" " , apiJson ) ;
622+ // Verify roundtripped JSON preserves the object structure
623+ Assert . Contains ( "\" message\" :\" Test error\" " , apiJson ) ;
624+ Assert . Contains ( "\" o_s_name\" :\" Windows\" " , apiJson ) ;
579625 }
580626
581627 [ Fact ]
0 commit comments