C# 为什么 System.Text.Json 不反序列化字符串
C# Why does System.Text.Json doesnt deserialise string
鉴于此模型 class:
using System.Text.Json;
public class QueueMessage
{
public int MerchantId { get; }
public string Message { get; }
}
当我尝试将 json 字符串反序列化为 QueueMessage 类型时,字段设置为默认值。 0 和空。
这就是我试图反序列化它的方式:
var jsonString = "{\"MerchantId\":2,\"Message\":\"Message 2\"}";
QueueMessage message = JsonSerializer.Deserialize<QueueMessage>(jsonString, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
message.Message is null
message.MerchantId is 0
顺便说一句,我正在使用.Net 5。
我尝试了什么
好吧,我尝试使用我的好友 Newtonsoft.Json
dynamic message = JsonConvert.DeserializeObject(jsonString);
dynamic mercId = message.MerchantId.Value; //THIS gives the expected value of 2
然而,
QueueMessage msg = JsonConvert.DeserializeObject<QueueMessage>(jsonString);
给出 0,空结果
现在,问题是为什么反序列化为类型对象会失败?
您需要为您的属性添加 setter,否则反序列化器无法为这些属性赋值
public class QueueMessage
{
public int MerchantId { get; set; }
public string Message { get; set; }
}
作为 Ziv 答案的替代方案,如果您重视构造类型 and/or 不变性的好处,System.Text.Json
now support \[JsonConstructor\]
, so you can now use constructed DTO types.[=35 的最新版本=]
在我看来,你应该 always 也指定一个明确的 [JsonPropertyName]
来防止 DTO/JSON 由重命名属性或更改引起的损坏project-wide camelCase
对比 PascalCase
JSON 序列化设置。
- 我还认为 JSON 对象成员应该始终使用
camelCase
,而不是 PascalCase
,因为这是 JavaScript/TypeScript 使用的大写约定,这是通常的目标对于 JSON 类型(unfortunately STJ defaults 到 PascalCase
(*grrrr*))
示例 1:使用简单的构造函数
- ...虽然这不是一个很好的例子,因为它没有显示 ctor 实际上做了任何有用的事情。
using System.Text.Json;
public class QueueMessage
{
[JsonConstructor]
public QueueMessage(
int merchantId,
string? message
)
{
this.MerchantId = merchantId;
this.Message = message;
}
[JsonPropertyName("merchantId")] public int MerchantId { get; }
[JsonPropertyName("message") ] public string? Message { get; }
}
示例 2:使用验证构造函数
- 因为构造函数可以进行断言和验证参数,这意味着 ctor 可以防止
QueueMessage.Message
成为 null
,所以 String?
属性 可以更改为String
这使得使用 DTO 更好 - 它也可以验证 MerchantId
:
using System.Text.Json;
public class QueueMessage
{
[JsonConstructor]
public QueueMessage(
int merchantId,
string message
)
{
this.MerchantId = merchantId > 0 ? merchantId : throw new ArgumentOutOfRangeException( paramName: nameof(merchantId), actualValue: merchantId, message: "Value must be positive and non-zero." );
this.Message = message ?? throw new ArgumentNullException(nameof(message));
}
/// <summary>Always >= 1.</summary>
[JsonPropertyName("merchantId")] public int MerchantId { get; }
/// <summary>Never null.</summary>
[JsonPropertyName("message") ] public string Message { get; }
}
示例 3:使用 record class
- 为了简单起见,您也可以只使用
record class
。
- 虽然这失去了验证逻辑,所以
Message
现在又是 String?
。
- 使用语法
[property: ]
通过构造函数参数名称将 JsonPropertyName
和其他属性应用于 record class
属性。
public record class QueueMessage(
[property: JsonPropertyName("merchantId")] int MerchantId,
[property: JsonPropertyName("message") ] string? Message
);
鉴于此模型 class:
using System.Text.Json;
public class QueueMessage
{
public int MerchantId { get; }
public string Message { get; }
}
当我尝试将 json 字符串反序列化为 QueueMessage 类型时,字段设置为默认值。 0 和空。 这就是我试图反序列化它的方式:
var jsonString = "{\"MerchantId\":2,\"Message\":\"Message 2\"}";
QueueMessage message = JsonSerializer.Deserialize<QueueMessage>(jsonString, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
message.Message is null
message.MerchantId is 0
顺便说一句,我正在使用.Net 5。
我尝试了什么 好吧,我尝试使用我的好友 Newtonsoft.Json
dynamic message = JsonConvert.DeserializeObject(jsonString);
dynamic mercId = message.MerchantId.Value; //THIS gives the expected value of 2
然而,
QueueMessage msg = JsonConvert.DeserializeObject<QueueMessage>(jsonString);
给出 0,空结果
现在,问题是为什么反序列化为类型对象会失败?
您需要为您的属性添加 setter,否则反序列化器无法为这些属性赋值
public class QueueMessage
{
public int MerchantId { get; set; }
public string Message { get; set; }
}
作为 Ziv 答案的替代方案,如果您重视构造类型 and/or 不变性的好处,
System.Text.Json
now support\[JsonConstructor\]
, so you can now use constructed DTO types.[=35 的最新版本=]在我看来,你应该 always 也指定一个明确的
[JsonPropertyName]
来防止 DTO/JSON 由重命名属性或更改引起的损坏project-widecamelCase
对比PascalCase
JSON 序列化设置。- 我还认为 JSON 对象成员应该始终使用
camelCase
,而不是PascalCase
,因为这是 JavaScript/TypeScript 使用的大写约定,这是通常的目标对于 JSON 类型(unfortunately STJ defaults 到PascalCase
(*grrrr*))
- 我还认为 JSON 对象成员应该始终使用
示例 1:使用简单的构造函数
- ...虽然这不是一个很好的例子,因为它没有显示 ctor 实际上做了任何有用的事情。
using System.Text.Json;
public class QueueMessage
{
[JsonConstructor]
public QueueMessage(
int merchantId,
string? message
)
{
this.MerchantId = merchantId;
this.Message = message;
}
[JsonPropertyName("merchantId")] public int MerchantId { get; }
[JsonPropertyName("message") ] public string? Message { get; }
}
示例 2:使用验证构造函数
- 因为构造函数可以进行断言和验证参数,这意味着 ctor 可以防止
QueueMessage.Message
成为null
,所以String?
属性 可以更改为String
这使得使用 DTO 更好 - 它也可以验证MerchantId
:
using System.Text.Json;
public class QueueMessage
{
[JsonConstructor]
public QueueMessage(
int merchantId,
string message
)
{
this.MerchantId = merchantId > 0 ? merchantId : throw new ArgumentOutOfRangeException( paramName: nameof(merchantId), actualValue: merchantId, message: "Value must be positive and non-zero." );
this.Message = message ?? throw new ArgumentNullException(nameof(message));
}
/// <summary>Always >= 1.</summary>
[JsonPropertyName("merchantId")] public int MerchantId { get; }
/// <summary>Never null.</summary>
[JsonPropertyName("message") ] public string Message { get; }
}
示例 3:使用 record class
- 为了简单起见,您也可以只使用
record class
。- 虽然这失去了验证逻辑,所以
Message
现在又是String?
。
- 虽然这失去了验证逻辑,所以
- 使用语法
[property: ]
通过构造函数参数名称将JsonPropertyName
和其他属性应用于record class
属性。
public record class QueueMessage(
[property: JsonPropertyName("merchantId")] int MerchantId,
[property: JsonPropertyName("message") ] string? Message
);