首页 > 解决方案 > 当属性和构造函数参数类型不同时,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 中,这是一个快速而干净的解决方案。

标签: jsonconstructorsystem.text.json.net-5

解决方案


这显然是一个已知的限制System.Text.Json。查看问题:

因此(至少在 .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只会序列化公共属性。但是,如果您使用settergetter标记属性(但不能同时使用两者[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首先破坏字符串。


推荐阅读