使用英语阅读

通过


使用不可变类型和属性

不可变 类型 是阻止在实例化对象后更改对象的任何属性或字段值。 该类型可以是记录、没有公共属性或字段、具有只读属性,或者包含具有专用资源库或仅初始化资源库的属性。 System.String 是不可变类型的一个示例。 System.Text.Json 提供了将 JSON 反序列化为不可变类型的不同方法。

参数化构造函数

默认情况下,System.Text.Json 使用默认的公共无参数构造函数。 但是,可以告诉它使用参数化构造函数,这样就可以反序列化不可变类或结构。

  • 对于类,如果唯一的构造函数是参数化构造函数,将使用该构造函数。

  • 对于结构或具有多个构造函数的类,请通过应用 [JsonConstructor] 属性来指定要使用的结构。 如果未使用该属性,则始终使用公共无参数构造函数(如果存在)。

    以下示例使用 [JsonConstructor] 属性:

    C#
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    namespace ImmutableTypes
    {
        public struct Forecast
        {
            public DateTime Date { get; }
            public int TemperatureC { get; }
            public string Summary { get; }
    
            [JsonConstructor]
            public Forecast(DateTime date, int temperatureC, string summary) =>
                (Date, TemperatureC, Summary) = (date, temperatureC, summary);
        }
    
        public class Program
        {
            public static void Main()
            {
                string json = """
                    {
                        "date":"2020-09-06T11:31:01.923395-07:00",
                        "temperatureC":-1,
                        "summary":"Cold"
                    }
                    """;
                Console.WriteLine($"Input JSON: {json}");
    
                var options = JsonSerializerOptions.Web;
    
                Forecast forecast = JsonSerializer.Deserialize<Forecast>(json, options);
    
                Console.WriteLine($"forecast.Date: {forecast.Date}");
                Console.WriteLine($"forecast.TemperatureC: {forecast.TemperatureC}");
                Console.WriteLine($"forecast.Summary: {forecast.Summary}");
    
                string roundTrippedJson =
                    JsonSerializer.Serialize<Forecast>(forecast, options);
    
                Console.WriteLine($"Output JSON: {roundTrippedJson}");
            }
        }
    }
    
    // Produces output like the following example:
    //
    //Input JSON: { "date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"summary":"Cold"}
    //forecast.Date: 9 / 6 / 2020 11:31:01 AM
    //forecast.TemperatureC: -1
    //forecast.Summary: Cold
    //Output JSON: { "date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"summary":"Cold"}
    

    在 .NET 7 和更早版本中,[JsonConstructor] 属性只能与公共构造函数一起使用。

参数化构造函数的参数名称必须与属性名称和类型匹配。 匹配不区分大小写,即使使用 [JsonPropertyName] 重命名属性,构造函数参数也必须与实际属性名称匹配。 在以下示例中,TemperatureC 属性的名称更改为 JSON 中的 celsius,但构造函数参数仍命名为 temperatureC

C#
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ImmutableTypesCtorParms
{
    public readonly struct Forecast
    {
        public DateTime Date { get; }
        [JsonPropertyName("celsius")]
        public int TemperatureC { get; }
        public string Summary { get; }

        [JsonConstructor]
        public Forecast(DateTime date, int temperatureC, string summary) =>
            (Date, TemperatureC, Summary) = (date, temperatureC, summary);
    }

    public class Program
    {
        public static void Main()
        {
            string json = """
                {
                    "date":"2020-09-06T11:31:01.923395-07:00",
                    "celsius":-1,
                    "summary":"Cold"
                }
                """;
            Console.WriteLine($"Input JSON: {json}");

            var options = JsonSerializerOptions.Web;

            Forecast forecast = JsonSerializer.Deserialize<Forecast>(json, options);

            Console.WriteLine($"forecast.Date: {forecast.Date}");
            Console.WriteLine($"forecast.TemperatureC: {forecast.TemperatureC}");
            Console.WriteLine($"forecast.Summary: {forecast.Summary}");

            string roundTrippedJson =
                JsonSerializer.Serialize<Forecast>(forecast, options);

            Console.WriteLine($"Output JSON: {roundTrippedJson}");

        }
    }
}

// Produces output like the following example:
//
//Input JSON: { "date":"2020-09-06T11:31:01.923395-07:00","celsius":-1,"summary":"Cold"}
//forecast.Date: 9 / 6 / 2020 11:31:01 AM
//forecast.TemperatureC: -1
//forecast.Summary: Cold
//Output JSON: { "date":"2020-09-06T11:31:01.923395-07:00","celsius":-1,"summary":"Cold"}

除了 [JsonPropertyName],以下属性还支持使用参数化构造函数进行反序列化:

记录

序列化和反序列化也支持记录,如以下示例所示:

C#
using System.Text.Json;

namespace Records
{
    public record Forecast(DateTime Date, int TemperatureC)
    {
        public string? Summary { get; init; }
    };

    public class Program
    {
        public static void Main()
        {
            Forecast forecast = new(DateTime.Now, 40)
            {
                Summary = "Hot!"
            };

            string forecastJson = JsonSerializer.Serialize<Forecast>(forecast);
            Console.WriteLine(forecastJson);
            Forecast? forecastObj = JsonSerializer.Deserialize<Forecast>(forecastJson);
            Console.WriteLine(forecastObj);
        }
    }
}

// Produces output like the following example:
//
//{ "Date":"2020-10-21T15:26:10.5044594-07:00","TemperatureC":40,"Summary":"Hot!"}
//Forecast { Date = 10 / 21 / 2020 3:26:10 PM, TemperatureC = 40, Summary = Hot! }

可以使用 property: 特性上的目标,将任何特性应用于属性名称。 有关位置记录的详细信息,请参阅 C# 语言参考中有关 记录 的文章。

非公共成员和属性访问器

可以使用 [JsonInclude] 属性启用非公共属性访问器,如下例所示

C#
using System.Text.Json;
using System.Text.Json.Serialization;

namespace NonPublicAccessors
{
    public class Forecast
    {
        public DateTime Date { get; init; }

        [JsonInclude]
        public int TemperatureC { get; private set; }

        [JsonInclude]
        public string? Summary { private get; set; }
    };

    public class Program
    {
        public static void Main()
        {
            string json = """
                {
                    "Date":"2020-10-23T09:51:03.8702889-07:00",
                    "TemperatureC":40,
                    "Summary":"Hot"
                }
                """;
            Console.WriteLine($"Input JSON: {json}");

            Forecast forecastDeserialized = JsonSerializer.Deserialize<Forecast>(json)!;
            Console.WriteLine($"Date: {forecastDeserialized.Date}");
            Console.WriteLine($"TemperatureC: {forecastDeserialized.TemperatureC}");

            json = JsonSerializer.Serialize<Forecast>(forecastDeserialized);
            Console.WriteLine($"Output JSON: {json}");
        }
    }
}

// Produces output like the following example:
//
//Input JSON: { "Date":"2020-10-23T09:51:03.8702889-07:00","TemperatureC":40,"Summary":"Hot"}
//Date: 10 / 23 / 2020 9:51:03 AM
//TemperatureC: 40
//Output JSON: { "Date":"2020-10-23T09:51:03.8702889-07:00","TemperatureC":40,"Summary":"Hot"}

通过包含具有专用资源库的属性,你仍然可以反序列化该属性。

在 .NET 8 及更高版本中,还可使用 [JsonInclude] 属性将非公共成员加入到给定类型的序列化协定中

备注

在源生成模式下,对于 private 成员或 private 访问器,不能通过使用 [JsonInclude] 属性对其进行注释来序列化成员或使用访问器。 仅当 internal 成员或 internal 访问器与生成的 JsonSerializerContext 位于同一程序集中时,才能序列化这些成员或使用这些访问器。

只读属性

在 .NET 8 及更高版本中,也可反序列化只读属性,或者没有专用或公共资源库的属性。 虽然无法更改属性引用的实例,但如果属性的类型是可变的,则可以对其进行修改。 例如,可以将元素添加到列表中。 若要反序列化只读属性,需要将其对象创建处理行为设置为 填充 而不是 替换。 例如,可以使用 JsonObjectCreationHandlingAttribute 属性对属性进行批注。

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

有关详细信息,请参阅填充初始化的属性

另请参阅