json - 当属性和构造函数参数类型不同时,System.Text.Json(但不是 Newtonsoft.Json)中的 JsonConstructorAttribute 会导致异常
问题描述
给定一个 Base64 字符串,以下示例类将使用Newtonsoft.Json正确反序列化,但不能使用System.Text.Json:
using System;
using System.Text.Json.Serialization;
public class AvatarImage{
public Byte[] Data { get; set; } = null;
public AvatarImage() {
}
[JsonConstructor]
public AvatarImage(String Data) {
//Remove Base64 header info, leaving only the data block and convert it to a Byte array
this.Data = Convert.FromBase64String(Data.Remove(0, Data.IndexOf(',') + 1));
}
}
使用 System.Text.Json,会引发以下异常:
must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.
显然 System.Text.Json 不喜欢属性是 Byte[] 但参数是 String 的事实,这并不重要,因为重点是构造函数应该负责分配。
有没有办法让它与 System.Text.Json 一起工作?
在我的特殊情况下,Base64 图像被发送到 WebAPI 控制器,但最终对象只需要 Byte[]。在 Newtonsoft 中,这是一个快速而干净的解决方案。
解决方案
这显然是一个已知的限制System.Text.Json
。查看问题:
- [JsonSerializer] 放宽对 ctor 参数类型到不可变属性类型匹配的限制 #44428当前标记为6.0.0里程碑。
- System.Text.Json 错误地要求构造参数类型以匹配不可变属性类型。#47422
- JsonConstrutor Newtonsoft.Json 和 System.Text.Json #46480 之间的不同行为。
因此(至少在 .Net 5 中)您将需要重构您的类以避免限制。
一种解决方案是添加一个代理 Base64 编码的字符串属性:
public class AvatarImage
{
[JsonIgnore]
public Byte[] Data { get; set; } = null;
[JsonInclude]
[JsonPropertyName("Data")]
public string Base64Data
{
private get => Data == null ? null : Convert.ToBase64String(Data);
set
{
var index = value.IndexOf(',');
this.Data = Convert.FromBase64String(index < 0 ? value : value.Remove(0, index + 1));
}
}
}
请注意,通常JsonSerializer
只会序列化公共属性。但是,如果您使用setter或getter标记属性(但不能同时使用两者[JsonInclude]
),则可以是非公开的。(我不知道为什么微软不允许两者都是私有的,数据契约序列化程序当然支持标记为的私有成员。)在这种情况下,我选择将 getter 设为私有以减少代理属性被其他一些人序列化的机会序列化程序或通过某些属性浏览器显示。[DataMember]
演示小提琴#1在这里。
或者,您可以JsonConverter<T>
为AvatarImage
[JsonConverter(typeof(AvatarConverter))]
public class AvatarImage
{
public Byte[] Data { get; set; } = null;
}
class AvatarConverter : JsonConverter<AvatarImage>
{
class AvatarDTO { public string Data { get; set; } }
public override AvatarImage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dto = JsonSerializer.Deserialize<AvatarDTO>(ref reader, options);
var index = dto.Data?.IndexOf(',') ?? -1;
return new AvatarImage { Data = dto.Data == null ? null : Convert.FromBase64String(index < 0 ? dto.Data : dto.Data.Remove(0, index + 1)) };
}
public override void Write(Utf8JsonWriter writer, AvatarImage value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, new { Data = value.Data }, options);
}
如果对于简单模型,这似乎是更容易的解决方案,但对于复杂模型或经常添加属性的模型可能会成为麻烦。
演示小提琴#2在这里。
最后,似乎有点不幸的是,该Data
属性在反序列化过程中会附加一些在序列化过程中不存在的额外标头。与其在反序列化期间修复此问题,不如考虑修改您的架构以避免Data
首先破坏字符串。
推荐阅读
- php - 限制 wordpress 页面中的几个类别
- php - 从 php 版本 7.0 切换到 7.1 后,PDF 创建不再有效
- javascript - 我对传感器的理解和js代码是否正确
- r - 如何从 CRAN(在 MacOS 上)在 R 上安装软件包?
- python - 如何将给定 URL 中的值导入 python
- java - 如何有效地使用 apache-poi 从 FTP 服务器读取大型 excel 文件?
- mfc - 检测是否使用 MFC 安装了 Opera 浏览器
- vue.js - 除了单击的按钮外,如何禁用按钮?
- c# - Is there any Solution for my code problem in webbrowser to identify between Select tag and option
- react-native - react-navigation 和 api 调用的 Redux 错误