Skip to content

If ByteArrayDeserializer gets an empty array, return null instead of calling Activator.CreateInstance#70

Merged
twogood merged 8 commits intomainfrom
fix-22
Oct 19, 2025
Merged

If ByteArrayDeserializer gets an empty array, return null instead of calling Activator.CreateInstance#70
twogood merged 8 commits intomainfrom
fix-22

Conversation

@twogood
Copy link
Owner

@twogood twogood commented May 29, 2025

TODO: Maybe make this behavior configurable before merge?

Resolves #22

Summary by CodeRabbit

  • Bug Fixes

    • Improved byte-array deserialization: directly return raw bytes for byte[] targets, return null for empty byte-array payloads when expecting an object, and raise an error for attempts to deserialize into value types.
  • Tests

    • Added tests covering empty-response scenarios for byte-array and byte-array-as-object deserialization.

…calling Activator.CreateInstance

TODO: Maybe make this behavior configurable before merge?

Resolves #22
@twogood twogood requested a review from Copilot May 29, 2025 21:09
@twogood twogood self-assigned this May 29, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates ByteArrayDeserializer to handle empty byte arrays differently by returning null instead of calling Activator.CreateInstance for non-byte[] types. Key changes include adding an early return for empty arrays in the deserializer and introducing new test cases to validate the behavior for both byte arrays and objects.

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs Added logic to return a null object when an empty byte array is encountered for types other than byte[].
Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs Added tests to cover scenarios for empty byte arrays for both byte[] and object deserialization.

@twogood
Copy link
Owner Author

twogood commented Sep 17, 2025

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Sep 17, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link

coderabbitai bot commented Sep 17, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds explicit handling in the byte-array deserializer: return raw bytes for byte[] targets, reject value types, return null for empty byte arrays when deserializing into non-value reference types, and preserve existing instance creation for non-empty arrays. Two tests added to validate empty-response behaviors.

Changes

Cohort / File(s) Summary
Deserializer null-handling logic
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs
Adds early-return for byte[] target type (returns raw bytes). Throws InvalidOperationException for value-type targets. For non-value reference types, returns null when the byte array is empty; otherwise uses existing Activator.CreateInstance(type, bytes) for non-empty arrays.
Empty array test coverage
Activout.RestClient.Test/NonJsonRestClientTests.cs
Adds TestGetEmptyByteArray (expects empty, non-null byte[] from empty ByteArrayContent) and TestGetByteArrayObjectWithEmptyArray (expects null when deserializing an empty byte array into an object).

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant ByteArrayDeserializer as Deserializer
    participant Activator

    Caller->>Deserializer: Deserialize(bytes, targetType)
    alt targetType == byte[]
        Deserializer-->>Caller: return bytes
    else targetType is value type
        Deserializer-->>Caller: throw InvalidOperationException
    else bytes.length == 0
        Deserializer-->>Caller: return null
    else
        Deserializer->>Activator: CreateInstance(targetType, bytes)
        Activator-->>Deserializer: instance
        Deserializer-->>Caller: return instance
    end
    note right of Deserializer #f0f4c3: New branches for empty bytes and byte[] target
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

I nibble on bytes both big and small,
When emptiness comes I answer the call,
If raw byte[] you truly seek,
I hand it back — no fuss, no tweak,
For empty objects I quietly bow — null for now. 🐰

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes Check ❓ Inconclusive The PR includes changes that extend beyond the specific requirement stated in issue #22. While the core objective addresses empty byte array handling returning null, the implementation also adds an early return for byte[] types and introduces an exception throw for value types—behaviors not explicitly mentioned in the linked issue objective. Without visibility into the original deserializer code, it is unclear whether these additional type-handling changes are refactoring necessary to properly implement the main feature or represent independently scoped additions outside issue #22's requirements. To clarify scope, please confirm whether the byte[] type handling (early return for raw bytes) and value type exception handling were pre-existing behaviors being refactored, or new behaviors being introduced as part of this PR. If these are new additions, consider documenting whether they address related issues or represent necessary supporting infrastructure for the main change, or if they should be separated into a different PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "If ByteArrayDeserializer gets an empty array, return null instead of calling Activator.CreateInstance" is clear, specific, and directly aligned with the main change in the changeset. The code changes in ByteArrayDeserializer.cs specifically implement this behavior by returning null for empty byte arrays instead of attempting to create an instance with Activator.CreateInstance. The supporting test cases further validate this exact behavior change. The title is concise, avoids vague terminology, and accurately reflects the primary objective of the changeset.
Linked Issues Check ✅ Passed The code changes directly address the objective stated in linked issue #22: "Change ByteArrayDeserializer behavior so that when it receives an empty byte array it returns null instead of calling Activator.CreateInstance." The implementation adds logic to detect empty byte arrays and return null for non-value types instead of invoking Activator.CreateInstance. The accompanying test cases validate this behavior through TestGetByteArrayObjectWithEmptyArray, which expects null when an empty byte array is received. The changes successfully meet the primary coding requirement specified in the linked issue.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-22

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10cf449 and b77ff77.

📒 Files selected for processing (1)
  • Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1)

22-25: Document the rationale (and configurability) in code.

Add a short comment explaining why empty payloads map to null (except byte[]), and reference the “consider making configurable” TODO from the PR description for future maintainers.

             if (bytes.Length == 0)
             {
-                return type == typeof(byte[]) ? bytes : null;
+                // Empty payload: for raw byte[] return the empty array; for other targets, treat as "no value".
+                // TODO: consider making this behavior configurable (see PR #70 / Issue #22).
+                return type == typeof(byte[]) ? bytes : null;
             }
🧹 Nitpick comments (3)
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1)

22-25: Guard against value-type targets on empty payloads.

If a consumer ever requests a value type (struct) here, returning null will fail downstream. Consider throwing a clearer exception instead.

             if (bytes.Length == 0)
             {
-                return type == typeof(byte[]) ? bytes : null;
+                if (type == typeof(byte[])) return bytes;
+                if (type.IsValueType)
+                    throw new InvalidOperationException($"Cannot deserialize empty application/octet-stream to value type '{type.FullName}'.");
+                return null;
             }
Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs (2)

484-491: Prefer Array.Empty() and set Content-Type explicitly.

Minor: avoid allocating new arrays and ensure the byte deserializer is chosen deterministically.

-            _mockHttp
-                .When($"{BaseUri}/movies/bytes")
-                .Respond(new ByteArrayContent(new byte[0]));
+            _mockHttp
+                .When($"{BaseUri}/movies/bytes")
+                .Respond(_ =>
+                {
+                    var content = new ByteArrayContent(Array.Empty<byte>());
+                    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+                    return content;
+                });

523-526: Apply the same header and allocation tweaks here.

Mirror the improvements from the other test for consistency.

-            _mockHttp
-                .When($"{BaseUri}/movies/byte-object")
-                .Respond(new ByteArrayContent(new byte[0]));
+            _mockHttp
+                .When($"{BaseUri}/movies/byte-object")
+                .Respond(_ =>
+                {
+                    var content = new ByteArrayContent(Array.Empty<byte>());
+                    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+                    return content;
+                });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e690246 and b7bab77.

📒 Files selected for processing (2)
  • Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs (2 hunks)
  • Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1 hunks)
🔇 Additional comments (3)
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1)

22-25: Behavior change aligns with Issue #22—good early return.

Returning null for non-byte[] targets on empty payloads matches the stated objective; byte[] continues to return the empty array. Looks good.

Please confirm there are no call sites that relied on Activator.CreateInstance for empty payloads (potential behavioral break).

Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs (2)

484-501: Nice coverage for empty byte[] case.

Assertions are precise: non-null and empty for byte[]. LGTM.


519-535: Good null-path assertion for object target.

Validates the new empty-payload behavior for non-byte[] types. LGTM.

@twogood twogood marked this pull request as ready for review October 18, 2025 11:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1)

11-14: Use collection expression syntax per coding guidelines.

The coding guidelines specify using [] list syntax for collections instead of new[].

Apply this diff:

-        public IReadOnlyCollection<MediaType> SupportedMediaTypes => new[]
-        {
-            MediaType.ValueOf("application/octet-stream")
-        };
+        public IReadOnlyCollection<MediaType> SupportedMediaTypes =>
+        [
+            MediaType.ValueOf("application/octet-stream")
+        ];

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7bab77 and 10cf449.

📒 Files selected for processing (2)
  • Activout.RestClient.Test/NonJsonRestClientTests.cs (2 hunks)
  • Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Use [] list syntax for collections
Use file-scoped namespaces
Use primary constructors wherever possible
Use records for data transfer objects (DTOs) and immutable data structures
Use var for local variable declarations when possible
Use expression-bodied members for simple methods and properties
Use pattern matching for type checks and deconstruction
Use using statements for resource management
Use async and await for asynchronous programming
Never include "Async" in method names
Only include comments that explain why something is done, not what is done

Files:

  • Activout.RestClient.Test/NonJsonRestClientTests.cs
  • Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs
🧬 Code graph analysis (1)
Activout.RestClient.Test/NonJsonRestClientTests.cs (2)
Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs (11)
  • Fact (52-76)
  • Fact (78-97)
  • Fact (114-148)
  • Fact (150-166)
  • Fact (168-188)
  • Fact (190-214)
  • Fact (216-243)
  • Fact (245-272)
  • Fact (274-290)
  • Fact (292-308)
  • Activout (16-310)
Activout.RestClient.Test.Json/RestClientTests.cs (1)
  • RestClientTests (29-348)
🔇 Additional comments (4)
Activout.RestClient/Serialization/Implementation/ByteArrayDeserializer.cs (2)

21-24: LGTM! Early return for byte[] target type is correct.

This appropriately returns the raw byte array immediately when the target type is byte[], avoiding unnecessary object instantiation and correctly handling both empty and non-empty arrays.


32-33: Empty array handling correctly implements the PR objective.

The null return for empty byte arrays resolves issue #22 as intended. The comment appropriately explains the rationale.

Note: A past review suggested adding a comment explaining why empty arrays return the bytes for byte[] types (line 23) but null for object types (line 33). While the current implementation is clear, you may consider adding a brief comment at line 21-24 if you want to address that feedback.

Based on learnings

Activout.RestClient.Test/NonJsonRestClientTests.cs (2)

232-248: LGTM! Test correctly validates byte[] return behavior for empty arrays.

This test properly verifies that when the target type is byte[], the deserializer returns an empty byte array rather than null, which aligns with the early return logic in ByteArrayDeserializer (lines 21-24).


267-282: LGTM! Test correctly validates null return behavior for empty arrays with object types.

This test properly verifies that when the target type is an object (not byte[]), the deserializer returns null for empty byte arrays, which aligns with the new behavior in ByteArrayDeserializer (line 33). Together with TestGetEmptyByteArray, these tests provide comprehensive coverage of the PR objective.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: David Eriksson <david@activout.se>
@twogood twogood merged commit 4727c34 into main Oct 19, 2025
9 checks passed
@twogood twogood deleted the fix-22 branch October 19, 2025 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If ByteArrayDeserializer gets an empty array, return null instead of calling Activator.CreateInstance

2 participants