Skip to content

JsonObjectCreationHandling.Populate not working with required and init with source generation #113838

Closed
@dabbinavo

Description

@dabbinavo

Description

When using System.Text json source generation mode, the JsonObjectCreationHandling.Populate option does not work correctly when having a required init property in the class.

Reproduction Steps

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

public class Program
{
    public static void Main()
    {
        var json = """{ "Text": {"Value": "Hello" }, "Values": [4, 5, 6] }""";

        // Deserialization using source generation
        Console.WriteLine("Deserialization using source generation");
        
        var working = JsonSerializer.Deserialize(json, GetterOnlyContext.Default.GetterOnly);
        Console.WriteLine(working);     // Hello 1, 2, 3, 4, 5, 6

        var notWorking = JsonSerializer.Deserialize(json, RequiredInitContext.Default.RequiredInit);
        Console.WriteLine(notWorking);  // Hello 1, 2, 3

        // Deserialization using options
        Console.WriteLine("Deserialization using options");
        var options = new JsonSerializerOptions { PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate };

        var working2 = JsonSerializer.Deserialize<GetterOnly>(json, options);
        Console.WriteLine(working2);    // Hello 1, 2, 3, 4, 5, 6

        var working3 = JsonSerializer.Deserialize<RequiredInit>(json, options);
        Console.WriteLine(working3);    // Hello 1, 2, 3, 4, 5, 6
    }
}

public class Text {
    public required string Value { get; set; }
}

public class GetterOnly
{
    public Text Text { get; } = new() { Value = "Default" };
    public IList<int> Values { get; } = [1, 2, 3];
    public override string ToString() => $"{Text.Value} {string.Join(", ", Values)}";
}

public class RequiredInit
{
    public required Text Text { get; init; }
    public IList<int> Values { get; } = [1, 2, 3];
    public override string ToString() => $"{Text.Value} {string.Join(", ", Values)}";
}

[JsonSourceGenerationOptions(
    PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate
)]
[JsonSerializable(typeof(GetterOnly))]
public partial class GetterOnlyContext : JsonSerializerContext { }

[JsonSourceGenerationOptions(
    PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate
)]
[JsonSerializable(typeof(RequiredInit))]
public partial class RequiredInitContext : JsonSerializerContext { }

Expected behavior

The Values from the json payload are appended to the existing IList<int> Values in all cases.

Actual behavior

Running the code snippet does not generate an runtime error, but does not append [4, 5, 6] in the notWorking-Variable case. Output:

Deserialization using source generation
Hello 1, 2, 3, 4, 5, 6
Hello 1, 2, 3
Deserialization using options
Hello 1, 2, 3, 4, 5, 6
Hello 1, 2, 3, 4, 5, 6

Regression?

No response

Known Workarounds

No response

Configuration

.NET SDK:
Version: 9.0.103
Commit: 96da45d427
Workload version: 9.0.100-manifests.ea610b94
MSBuild version: 17.12.24+90b52dda6

Laufzeitumgebung:
OS Name: Windows
OS Version: 10.0.26100
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\9.0.103\

Installierte .NET-Workloads:
Es sind keine installierten Workloads zum Anzeigen vorhanden.
Konfiguriert für die Verwendung loose manifests beim Installieren neuer Manifeste.

Host:
Version: 9.0.2
Architecture: x64
Commit: 80aa709

.NET SDKs installed:
9.0.102 [C:\Program Files\dotnet\sdk]
9.0.103 [C:\Program Files\dotnet\sdk]

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions