Fyll i initierade egenskaper

Från och med .NET 8 kan du ange en inställning för att antingen ersätta eller fylla i .NET-egenskaper när JSON deserialiseras. Uppräkningen JsonObjectCreationHandling innehåller alternativen för hantering av objektskapande:

Standardbeteende (ersätt)

Deserialiseraren System.Text.Json skapar alltid en ny instans av måltypen. Men även om en ny instans skapas kan vissa egenskaper och fält redan initieras som en del av objektets konstruktion. Tänk på följande typ:

class A
{
    public List<int> Numbers1 { get; } = [1, 2, 3];
    public List<int> Numbers2 { get; set; } = [1, 2, 3];
}

När du skapar en instans av den här klassen Numbers1 är egenskapen (och Numbers2) värdet en lista med tre element (1, 2 och 3). Om du deserialiserar JSON till den här typen är standardbeteendet att egenskapsvärden ersätts:

  • För Numbers1, eftersom den är skrivskyddad (ingen setter) har den fortfarande värdena 1, 2 och 3 i listan.
  • För Numbers2, som är skrivskyddad, allokeras en ny lista och värdena från JSON läggs till.

Om du till exempel kör följande deserialiseringskod innehåller Numbers1 värdena 1, 2 och 3 och Numbers2 innehåller värdena 4, 5 och 6.

A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");

Fylla i beteende

Från och med .NET 8 kan du ändra deserialiseringsbeteendet för att ändra (fylla i) egenskaper och fält i stället för att ersätta dem:

  • För en samlingstypegenskap återanvänds objektet utan att rensas. Om samlingen är förifyllda med element visas de i det slutliga deserialiserade resultatet tillsammans med värdena från JSON. Ett exempel finns i Exempel på samlingsegenskap.

  • För en egenskap som är ett objekt med egenskaper uppdateras dess föränderliga egenskaper till JSON-värdena, men själva objektreferensen ändras inte.

  • För en structtypegenskap är det effektiva beteendet att för dess föränderliga egenskaper behålls alla befintliga värden och nya värden från JSON läggs till. Men till skillnad från en referensegenskap återanvänds inte själva objektet eftersom det är en värdetyp. I stället ändras en kopia av structen och tilldelas sedan om till egenskapen. Ett exempel finns i Exempel på Struct-egenskap.

    En struct-egenskap måste ha en setter. annars utlöses en InvalidOperationException vid körning.

Kommentar

Det ifyllda beteendet fungerar för närvarande inte för typer som har en parametriserad konstruktor. Mer information finns i dotnet/runtime issue 92877.

Skrivskyddade egenskaper

För att fylla i referensegenskaper som är föränderliga, eftersom den instans som egenskapen refererar till inte ersätts, behöver egenskapen inte ha en setter. Det här beteendet innebär att deserialisering också kan fylla i skrivskyddade egenskaper.

Kommentar

Struct-egenskaper kräver fortfarande setters eftersom instansen ersätts med en ändrad kopia.

Exempel på samlingsegenskap

Överväg samma klass A från exemplet med ersättningsbeteende, men den här gången kommenterad med en inställning för att fylla i egenskaper i stället för att ersätta dem:

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class A
{
    public List<int> Numbers1 { get; } = [1, 2, 3];
    public List<int> Numbers2 { get; set; } = [1, 2, 3];
}

Om du kör följande deserialiseringskod, både Numbers1 och Numbers2 innehåller värdena 1, 2, 3, 4, 5 och 6:

A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");

Exempel på struct-egenskap

Följande klass innehåller en struct-egenskap, , S1vars deserialiseringsbeteende är inställt på Populate. När du har kört den här koden c.S1.Value1 har du värdet 10 (från konstruktorn) och c.S1.Value2 har värdet 5 (från JSON).

C? c = JsonSerializer.Deserialize<C>("""{"S1": {"Value2": 5}}""");

class C
{
    public C()
    {
        _s1 = new S
        {
            Value1 = 10
        };
    }

    private S _s1;

    [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
    public S S1
    {
        get { return _s1; }
        set { _s1 = value; }
    }
}

struct S
{
    public int Value1 { get; set; }
    public int Value2 { get; set; }
}

Om standardbeteendet Replace användes i stället skulle c.S1.Value1 standardvärdet vara 0 efter deserialisering. Det beror på att konstruktorn C() anropas och anger c.S1.Value1 till 10, men sedan ersätts värdet för S1 med en ny instans. (c.S1.Value2 skulle fortfarande vara 5, eftersom JSON ersätter standardvärdet.)

Så här anger du

Det finns flera sätt att ange en inställning för att ersätta eller fylla i:

  • JsonObjectCreationHandlingAttribute Använd attributet för att kommentera på typ- eller egenskapsnivå. Om du anger attributet på typnivå och anger dess Handling egenskap till Populategäller beteendet endast för de egenskaper där populationen är möjlig (till exempel måste värdetyper ha en setter).

    Om du vill att den typomfattande inställningen ska vara Populate, men vill undanta en eller flera egenskaper från det beteendet, kan du lägga till attributet på typnivå och igen på egenskapsnivå för att åsidosätta det ärvda beteendet. Det mönstret visas i följande kod.

    // Type-level preference is Populate.
    [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
    class B
    {
        // For this property only, use Replace behavior.
        [JsonObjectCreationHandling(JsonObjectCreationHandling.Replace)]
        public List<int> Numbers1 { get; } = [1, 2, 3];
        public List<int> Numbers2 { get; set; } = [1, 2, 3];
    }
    
  • Ange JsonSerializerOptions.PreferredObjectCreationHandling (eller, för källgenerering, JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling) för att ange en global inställning.

    var options = new JsonSerializerOptions
    {
        PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate
    };