Skip to content

Commit 1ec2439

Browse files
committed
refactor: enhance JSON deserialization logic; improve error handling and add tests for various scenarios
1 parent 6f10a09 commit 1ec2439

File tree

2 files changed

+153
-56
lines changed

2 files changed

+153
-56
lines changed

libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs

Lines changed: 18 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -54,97 +54,59 @@ public PowertoolsKafkaJsonSerializer(JsonSerializerContext serializerContext) :
5454
}
5555

5656
/// <summary>
57-
/// Deserializes binary data using JSON format.
57+
/// Deserializes complex (non-primitive) types using JSON format.
5858
/// </summary>
5959
/// <param name="data">The binary data to deserialize.</param>
6060
/// <param name="targetType">The type to deserialize to.</param>
6161
/// <param name="isKey">Whether this data represents a key (true) or a value (false).</param>
6262
/// <returns>The deserialized object.</returns>
6363
[RequiresDynamicCode("JSON deserialization might require runtime code generation.")]
6464
[RequiresUnreferencedCode("JSON deserialization might require types that cannot be statically analyzed.")]
65-
protected override object? DeserializeFormatSpecific(byte[] data,
65+
protected override object? DeserializeComplexTypeFormat(byte[] data,
6666
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties |
6767
DynamicallyAccessedMemberTypes.PublicFields)]
6868
Type targetType, bool isKey)
6969
{
70-
try
71-
{
72-
// Handle primitive types directly
73-
if (IsPrimitiveOrSimpleType(targetType))
74-
{
75-
return DeserializePrimitiveValue(data, targetType);
76-
}
77-
78-
// Convert bytes to JSON string
79-
var jsonStr = Encoding.UTF8.GetString(data);
80-
81-
if (SerializerContext != null)
82-
{
83-
// Try to get type info from context for AOT compatibility
84-
var typeInfo = SerializerContext.GetTypeInfo(targetType);
85-
if (typeInfo != null)
86-
{
87-
var result = JsonSerializer.Deserialize(jsonStr, typeInfo);
88-
if (result != null)
89-
{
90-
return result;
91-
}
92-
}
93-
}
94-
95-
// Fallback to regular deserialization
96-
#pragma warning disable IL2026, IL3050
97-
return JsonSerializer.Deserialize(jsonStr, targetType, JsonOptions);
98-
#pragma warning restore IL2026, IL3050
99-
}
100-
catch
70+
if (data == null || data.Length == 0)
10171
{
102-
// If deserialization fails, return null or default
10372
return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
10473
}
105-
}
106-
107-
/// <summary>
108-
/// Deserializes complex (non-primitive) types using JSON format.
109-
/// </summary>
110-
/// <param name="data">The binary data to deserialize.</param>
111-
/// <param name="targetType">The type to deserialize to.</param>
112-
/// <param name="isKey">Whether this data represents a key (true) or a value (false).</param>
113-
/// <returns>The deserialized object.</returns>
114-
[RequiresDynamicCode("JSON deserialization might require runtime code generation.")]
115-
[RequiresUnreferencedCode("JSON deserialization might require types that cannot be statically analyzed.")]
116-
protected override object? DeserializeComplexTypeFormat(byte[] data,
117-
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties |
118-
DynamicallyAccessedMemberTypes.PublicFields)]
119-
Type targetType, bool isKey)
120-
{
74+
12175
try
12276
{
12377
// Convert bytes to JSON string
12478
var jsonStr = Encoding.UTF8.GetString(data);
125-
79+
80+
// First try context-based deserialization if available
12681
if (SerializerContext != null)
12782
{
12883
// Try to get type info from context for AOT compatibility
12984
var typeInfo = SerializerContext.GetTypeInfo(targetType);
13085
if (typeInfo != null)
13186
{
132-
var result = JsonSerializer.Deserialize(jsonStr, typeInfo);
133-
if (result != null)
87+
try
88+
{
89+
var result = JsonSerializer.Deserialize(jsonStr, typeInfo);
90+
if (result != null)
91+
{
92+
return result;
93+
}
94+
}
95+
catch
13496
{
135-
return result;
97+
// Continue to fallback if context-based deserialization fails
13698
}
13799
}
138100
}
139101

140-
// Fallback to regular deserialization
102+
// Fallback to regular deserialization - this should handle types not in the context
141103
#pragma warning disable IL2026, IL3050
142104
return JsonSerializer.Deserialize(jsonStr, targetType, JsonOptions);
143105
#pragma warning restore IL2026, IL3050
144106
}
145107
catch
146108
{
147-
// If deserialization fails, return null or default
109+
// If all deserialization attempts fail, return null or default
148110
return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
149111
}
150112
}

libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,141 @@ private string CreateKafkaEvent(string keyValue, string valueValue)
391391
}}
392392
}}";
393393
}
394+
395+
[Fact]
396+
public void DirectJsonSerializerTest_InvokesFormatSpecificMethod()
397+
{
398+
// This test directly tests the JSON serializer methods
399+
var serializer = new TestJsonDeserializer();
400+
401+
// Create test data with valid JSON
402+
var testModel = new TestModel { Name = "DirectTest", Value = 555 };
403+
var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel));
404+
405+
// Act
406+
var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false);
407+
408+
// Assert
409+
Assert.NotNull(result);
410+
var model = result as TestModel;
411+
Assert.NotNull(model);
412+
Assert.Equal("DirectTest", model!.Name);
413+
Assert.Equal(555, model.Value);
414+
}
415+
416+
[Fact]
417+
public void DirectJsonSerializerTest_WithContext_UsesContext()
418+
{
419+
// Create a context that includes TestModel
420+
var options = new JsonSerializerOptions();
421+
var context = new TestJsonSerializerContext(options);
422+
423+
// Create the serializer with context
424+
var serializer = new TestJsonDeserializer(context);
425+
426+
// Create test data with valid JSON
427+
var testModel = new TestModel { Name = "ContextTest", Value = 999 };
428+
var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel));
429+
430+
// Act - directly test the protected method
431+
var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false);
432+
433+
// Assert
434+
Assert.NotNull(result);
435+
var model = result as TestModel;
436+
Assert.NotNull(model);
437+
Assert.Equal("ContextTest", model!.Name);
438+
Assert.Equal(999, model.Value);
439+
}
440+
441+
[Fact]
442+
public void DirectJsonSerializerTest_WithInvalidJson_ReturnsNullForReferenceType()
443+
{
444+
// Create the serializer
445+
var serializer = new TestJsonDeserializer();
446+
447+
// Create invalid JSON data
448+
var invalidJsonBytes = Encoding.UTF8.GetBytes("{ not valid json");
449+
450+
// Act - directly test the protected method
451+
var result = serializer.TestDeserializeFormatSpecific(invalidJsonBytes, typeof(TestModel), false);
452+
453+
// Assert - should return null for reference type when JSON is invalid
454+
Assert.Null(result);
455+
}
456+
457+
[Fact]
458+
public void DirectJsonSerializerTest_WithInvalidJson_ReturnsDefaultForValueType()
459+
{
460+
// Create the serializer
461+
var serializer = new TestJsonDeserializer();
462+
463+
// Create invalid JSON data
464+
var invalidJsonBytes = Encoding.UTF8.GetBytes("{ not valid json");
465+
466+
// Act - directly test the protected method with a value type
467+
var result = serializer.TestDeserializeFormatSpecific(invalidJsonBytes, typeof(int), false);
468+
469+
// Assert - should return default (0) for value type when JSON is invalid
470+
Assert.Equal(0, result);
471+
}
472+
473+
[Fact]
474+
public void DirectJsonSerializerTest_WithEmptyJson_ReturnsNullOrDefault()
475+
{
476+
// Create the serializer
477+
var serializer = new TestJsonDeserializer();
478+
479+
// Create empty JSON data
480+
var emptyJsonBytes = Array.Empty<byte>();
481+
482+
// Act - test with reference type
483+
var resultRef = serializer.TestDeserializeFormatSpecific(emptyJsonBytes, typeof(TestModel), false);
484+
// Act - test with value type
485+
var resultVal = serializer.TestDeserializeFormatSpecific(emptyJsonBytes, typeof(int), false);
486+
487+
// Assert
488+
Assert.Null(resultRef); // Reference type should get null
489+
Assert.Equal(0, resultVal); // Value type should get default
490+
}
491+
492+
[Fact]
493+
public void DirectJsonSerializerTest_WithContextResultingInNull_ReturnsNull()
494+
{
495+
// Create context
496+
var options = new JsonSerializerOptions();
497+
var context = new TestJsonSerializerContext(options);
498+
499+
// Create serializer with context
500+
var serializer = new TestJsonDeserializer(context);
501+
502+
// Create JSON that is "null"
503+
var jsonBytes = Encoding.UTF8.GetBytes("null");
504+
505+
// Act - even with context, null JSON should return null
506+
var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false);
507+
508+
// Assert
509+
Assert.Null(result);
510+
}
511+
512+
/// <summary>
513+
/// Test helper to directly access protected methods
514+
/// </summary>
515+
private class TestJsonDeserializer : PowertoolsKafkaJsonSerializer
516+
{
517+
public TestJsonDeserializer() : base() { }
518+
519+
public TestJsonDeserializer(JsonSerializerOptions options) : base(options) { }
520+
521+
public TestJsonDeserializer(JsonSerializerContext context) : base(context) { }
522+
523+
public object? TestDeserializeFormatSpecific(byte[] data, Type targetType, bool isKey)
524+
{
525+
// Call the protected method directly
526+
return base.DeserializeComplexTypeFormat(data, targetType, isKey);
527+
}
528+
}
394529
}
395530

396531
[JsonSerializable(typeof(TestModel))]

0 commit comments

Comments
 (0)