类型上的避难化构造函数中的每个参数必须绑定到避难所化的对象属性或字段

发布于 2025-01-24 07:52:42 字数 1468 浏览 0 评论 0原文

我有以下简单的课程:

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);
        }
    }
}

我知道可能有很多改进的空间(我很高兴听到它)。但是,方法delelialize给出了

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);

我在做什么错,一般应该改变什么?

I have the following simple classes :

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);
        }
    }
}

I know there is probably a LOT of room for improvement ( and I a am happy to hear about it ). However, the method Deserialize gives me the error

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.

For testing if this method works as intended I use this approach (which also throws the error)

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);

What am I doing wrong and what should I change in general?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清眉祭 2025-01-31 07:52:42

您正在遇到与参数化构造函数有关的两个单独的问题。如文档页面中所述, 使用system.text.json 使用不可变的类型和非公共配件:

system.text.json可以使用公共参数化的构造函数,这使得可以使不可变的类或结构进行挑选。对于一个类,如果唯一的构造函数是一个参数化的构造函数,则将使用该构造函数。对于一个结构或具有多个构造函数的类,请通过应用 [JSONCONSTRUCTOR] 属性。当不使用属性时,如果存在,请始终使用公共参数的构造函数。该属性只能与公共构造函数一起使用。

...

参数化构造函数的参数名称必须匹配属性名称​​和type 。匹配是不敏感的,即使您使用 [jsonpropertyname] 重命名属性。 [1]

您的第一个问题是类型<代码>存储库。您不会在问题中显示它,但我认为它看起来像这样:

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)或

    >

  2. 添加具有相同类型和名称的参数的构造函数,属性gitdirectory,并用[jsonconstructor]

    标记

采用选项#2,您的存储库类型现在应该看起来像:

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则对应于文件。因此,当挑选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年更新。当时提出了该问题,文档

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

You are encountering two separate problems related to deserializing types with parameterized constructors. As explained in the documentation page How to use immutable types and non-public accessors with System.Text.Json:

System.Text.Json can use a public parameterized constructor, which makes it possible to deserialize an immutable class or struct. For a class, if the only constructor is a parameterized one, that constructor will be used. For a struct, or a class with multiple constructors, specify the one to use by applying the [JsonConstructor] attribute. When the attribute is not used, a public parameterless constructor is always used if present. The attribute can only be used with public constructors.

...

The parameter names of a parameterized constructor must match the property names and types. Matching is case-insensitive, and the constructor parameter must match the actual property name even if you use [JsonPropertyName] to rename a property. [1]

Your first problem is with the type Repository. You don't show it in your question, but I assume it looks something like this:

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());
}

If so, your problem here is that either the name of the constructor argument corresponding to GitDirectory is not the same as the property name or the type of the argument is not the same.

Demo fiddle #1 here.

To fix this, you must either:

  1. Add a public parameterless constructor and make Repository be mutable (i.e. add a setter for GitDirectory), or

  2. Add a constructor with an argument of the same type and name as the property GitDirectory, and mark it with [JsonConstructor].

Adopting option #2, your Repository type should now look like:

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; }
}

And now Respository will deserialize successfully. Demo fiddle #2 here.

However, you will now encounter your second problem, namely that the Blob type will not round-trip either. In this case, Blob does have a unique parameterized constructor whose argument names and types correspond precisely to properties -- but the semantics of one of them, data, are completely different:

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;
    }

The property Data corresponds to the textual contents of a file, while the argument data corresponds to the file name of a file. Thus when deserializing Blob your code will attempt to read a file whose name equals the file's contents, and fail.

This inconsistency is, in my opinion, poor programming style, and likely to confuse other developers as well as System.Text.Json. Instead, consider adding factory methods to create a Blob from a file, or from file contents, and remove the corresponding constructor argument. Thus your Blob should look like:

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;
    }
}

And you would construct and round-trip it as follows:

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

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

Final working demo fiddle here.


[1] The documentation was updated in 2023. At the time this question was asked, the documentation merely stated

The parameter names of a parameterized constructor must match the property names.

记忆消瘦 2025-01-31 07:52:42

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

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

In my case I was inheriting from an abstract class. This class had an empty constructor and so did my child class. I even tried a custom JsonConverter for my data object class I was passing to my controller, which was receiving it using the [FromBody] annotation as a parameter.

That did not help.. my problem was my abstract class did not have { get; set; } on its public properties. I created this class with a parameterized constructor to avoid it being an anemic domain model, but did not think it would need the getters and setters. Be sure the JSON Property names (if you have attributes) also match. I spent hours on this one!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文