类型的反序列化构造函数中的每个参数都必须绑定到反序列化时的对象属性或字段

问题描述 投票:0回答:2

我有以下简单的课程:

public abstract class GitObject
{
    public Repository Repository { get; set; }
    public abstract string Serialize();
    public abstract void Deserialize(string data);

    public class Blob : GitObject
    {
        public string Data { get; set; }

        public Blob(Repository repository, string data = null)
        {
            if (data != null) Data = File.ReadAllText(data);
            Repository = repository;
        }
        public override string Serialize()
        {
            return JsonSerializer.Serialize(this);
        }
        public override void Deserialize(string data)
        {
            Blob blobData = JsonSerializer.Deserialize<Blob>(data);
        }
    }
}

我知道可能还有很大的改进空间(我很高兴听到这一点)。但是,该方法

Deserialize
给了我错误

Each parameter in the deserialization constructor on type 'CustomGit.Repository' 
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.

为了测试此方法是否按预期工作,我使用此方法(也会引发错误)

FileInfo file = new FileInfo(Path.Combine(repository.GitDirectory.FullName, "code.txt"));

GitObject.Blob firstBlob = new GitObject.Blob(repository, file.FullName);
var json = firstBlob.Serialize();

GitObject.Blob secondBlob = new GitObject.Blob(repository);
secondBlob.Deserialize(json);

我做错了什么?一般来说我应该改变什么?

c# system.text.json jsonserializer
2个回答
28
投票

您遇到了两个与使用参数化构造函数反序列化类型相关的独立问题。 如文档页面 How to use immutable types and non-public accessors with System.Text.Json:

中所述

System.Text.Json
可以使用公共参数化构造函数,这使得反序列化不可变的类或结构成为可能。对于一个类,如果唯一的构造函数是参数化构造函数,则将使用该构造函数。对于结构体或具有多个构造函数的类,请通过应用
[JsonConstructor]
属性来指定要使用的构造函数。当不使用该属性时,始终使用公共无参数构造函数(如果存在)。该属性只能与公共构造函数一起使用。

...

参数化构造函数的参数名称必须与属性名称和类型匹配。匹配不区分大小写,即使使用

[JsonPropertyName]
重命名属性,构造函数参数也必须与实际属性名称匹配。 [1]

您的第一个问题是类型

Repository
。 您没有在问题中显示它,但我认为它看起来像这样:

public class Repository
{
    public Repository(string gitDirectory) => this.GitDirectory = new DirectoryInfo(gitDirectory);

    [JsonConverter(typeof(DirectoryInfoConverter))]
    public DirectoryInfo GitDirectory { get; }
}

public class DirectoryInfoConverter : JsonConverter<DirectoryInfo>
{
    public override DirectoryInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        new DirectoryInfo(reader.GetString());
    public override void Write(Utf8JsonWriter writer, DirectoryInfo value, JsonSerializerOptions options) =>
        writer.WriteStringValue(value.ToString());
}

如果是这样,你这里的问题是要么

GitDirectory
对应的构造函数参数名称与属性名称不一样或者参数类型不一样

演示小提琴#1 这里

要解决此问题,您必须:

  1. 添加一个公共无参数构造函数并使

    Repository
    可变(即为
    GitDirectory
    添加一个 setter),或者

  2. 添加一个构造函数,其参数的类型和名称与属性

    GitDirectory
    相同,并用
    [JsonConstructor]
    标记。

采用选项 #2,您的

Repository
类型现在应如下所示:

public class Repository
{
    public Repository(string gitDirectory) => this.GitDirectory = new DirectoryInfo(gitDirectory);
    [JsonConstructor]
    public Repository(DirectoryInfo gitDirectory) => this.GitDirectory = gitDirectory ?? throw new ArgumentNullException(nameof(gitDirectory));

    [JsonConverter(typeof(DirectoryInfoConverter))]
    public DirectoryInfo GitDirectory { get; }
}

现在

Respository
将成功反序列化。 演示小提琴 #2 这里.

但是,您现在将遇到第二个问题,即

Blob
类型也不会往返。 在这种情况下,
Blob
确实有一个独特的参数化构造函数,其参数名称和类型与属性精确对应 - 但其中一个 data语义
 完全不同:

public class Blob : GitObject
{
    public string Data { get; set; }

    public Blob(Repository repository, string data = null)
    {
        if (data != null) 
            Data = File.ReadAllText(data);
        Repository = repository;
    }

属性

Data
对应于文件的文本内容,而参数
data
对应于文件的文件名。 因此,当反序列化
Blob
时,您的代码将尝试读取名称等于文件内容的文件,但会失败。

在我看来,这种不一致是糟糕的编程风格,并且可能会让其他开发人员以及 System.Text.Json 感到困惑。 相反,请考虑添加工厂方法以从文件或文件内容创建

Blob
,并删除相应的构造函数参数。 因此你的
Blob
应该看起来像:

public class Blob : GitObject
{
    public string Data { get; set; }

    public Blob(Repository repository) => this.Repository = repository ?? throw new ArgumentNullException(nameof(repository));

    public static Blob CreateFromDataFile(Repository repository, string dataFileName) =>
        new Blob(repository)
        {
            Data = File.ReadAllText(dataFileName),
        };
    
    public static Blob CreateFromDataConents(Repository repository, string data) =>
        new Blob(repository)
        {
            Data = data,
        };
    
    public override string Serialize() => JsonSerializer.Serialize(this);

    public override void Deserialize(string data)
    {
        // System.Text.Json does not have a Populate() method so we have to do it manually, or via a tool like AutoMapper
        Blob blobData = JsonSerializer.Deserialize<Blob>(data);
        this.Repository = blobData.Repository;
        this.Data = blobData.Data;
    }
}

您将按如下方式构建和往返它:

var firstBlob = GitObject.Blob.CreateFromDataFile(repository, file.FullName);
var json = firstBlob.Serialize();

var secondBlob = new GitObject.Blob(repository);
secondBlob.Deserialize(json);           

最终工作演示小提琴这里


[1] 该文档于 2023 年更新。在提出这个问题时,文档仅说明了

参数化构造函数的参数名称必须与属性名称匹配。


0
投票

就我而言,我是从抽象类继承的。这个类有一个空的构造函数,我的子类也是如此。我什至为我传递给控制器的数据对象类尝试了自定义 JsonConverter,该控制器使用 [FromBody] 注释作为参数接收它。

那没有帮助..我的问题是我的抽象类没有 { get;放; } 在其公共属性上。我使用参数化构造函数创建了此类,以避免它成为贫乏的域模型,但认为它不需要 getter 和 setter。确保 JSON 属性名称(如果有属性)也匹配。我在这个上花了几个小时!

© www.soinside.com 2019 - 2024. All rights reserved.