Skip to content

[JSON] Fix source generator serializing null byte[] as empty string#129834

Open
lezzi wants to merge 6 commits into
dotnet:mainfrom
lezzi:fix/stj-sourcegen-null-byte-array
Open

[JSON] Fix source generator serializing null byte[] as empty string#129834
lezzi wants to merge 6 commits into
dotnet:mainfrom
lezzi:fix/stj-sourcegen-null-byte-array

Conversation

@lezzi

@lezzi lezzi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #129833

Description

The System.Text.Json source generator's fast path serialized a null byte[] as an empty string ("") instead of null, diverging from the reflection-based ByteArrayConverter.

This happens because the emitted writer.WriteBase64String(value) implicitly converts the null byte[] to a ReadOnlySpan<byte>, where null and empty are indistinguishable. Unlike WriteString/WriteStringValue (which take string? and null-check internally), there is no WriteBase64String overload accepting a nullable byte[] — a span can't carry null — so the check can't live in the writer call and must be emitted by the generator, mirroring what ByteArrayConverter.Write already does.

The fix adds an explicit null check in the fast-path serialization helpers for JsonPrimitiveTypeKind.ByteArray, so null byte[] serializes as null for both object properties and collection elements.

Testing

Extended ClassWithNullableProperties_Roundtrip to cover byte[] — the existing all-null case now exercises the regression (with the bug, a null byte[] serialized to "" round-trips back to an empty array, not null). Runs across the reflection, metadata, and serialization (fast-path) contexts.

The source generator fast path emitted `writer.WriteBase64String(value)`
directly for byte[] members. Since byte[] implicitly converts to an empty
ReadOnlySpan<byte> when null, a null byte[] was serialized as an empty
Base64 string ("") instead of null, diverging from the reflection-based
ByteArrayConverter which writes null.

Emit an explicit null check in the fast-path serialization helpers for
JsonPrimitiveTypeKind.ByteArray, mirroring ByteArrayConverter.Write, so
that null byte[] members serialize as null for both object properties and
collection elements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 25, 2026 01:55
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Jun 25, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

@lezzi

lezzi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adjusts the System.Text.Json source generator’s fast-path serialization for byte[] so that null byte arrays are emitted as JSON null (rather than being treated like an empty span and written as an empty Base64 string), and extends existing nullable roundtrip tests to cover the byte[] case.

Changes:

  • Emit an explicit null handling branch when writing JsonPrimitiveTypeKind.ByteArray values/properties in generated fast-path code.
  • Extend nullable roundtrip tests to include a byte[]? property and validate both non-null and all-null instances roundtrip correctly.

Reviewed changes

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

File Description
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs Adds ByteArray to the nullable-properties roundtrip setup/assertions for the serialization-only context.
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs Adds ByteArray to the shared nullable-properties model and roundtrip assertions, covering the all-null regression path.
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Updates fast-path primitive emission for ByteArray to write JSON null for null byte arrays.

Comment thread src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Comment thread src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@lezzi

lezzi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Will wait for issue feedback, before polishing this further.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 25, 2026 06:38
@eiriktsarpalis eiriktsarpalis marked this pull request as ready for review June 25, 2026 06:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Outdated
Comment thread src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Outdated
- Fix indentation of else-if blocks to match surrounding code
- Replace 'is byte[] __byteArray' pattern with 'is not null' to avoid
  local variable name collisions when a type has multiple byte[] properties

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds explicit "ByteArray":null assertions to the source-gen
ClassWithNullableProperties_Roundtrip tests (covering the serialization
fast path) and a new ClassWithMultipleByteArrayProperties_Roundtrip theory
exercising a type with three byte[] properties to guard against emitter
local-name collisions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis and others added 2 commits June 25, 2026 12:04
Replace the inline if/else null checks emitted for byte[] values in the
fast-path serializer with a single on-demand `WriteByteArrayValue` private
static helper on the generated context. The value is passed as a method
argument so it is evaluated exactly once, avoiding the duplicate
value-expression emission (and associated variable-capture concerns) of the
previous inline approach.

The helper is emitted only when a byte[] value is actually serialized,
following the existing `_emitValueTypeSetterDelegate` on-demand pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Text.Json community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.Text.Json source generator emits "" for null byte[] properties

4 participants