我有以下简单的课程:
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);
我做错了什么?一般来说我应该改变什么?
您遇到了两个与使用参数化构造函数反序列化类型相关的独立问题。 如文档页面 How to use immutable types and non-public accessors with System.Text.Json:
中所述可以使用公共参数化构造函数,这使得反序列化不可变的类或结构成为可能。对于一个类,如果唯一的构造函数是参数化构造函数,则将使用该构造函数。对于结构体或具有多个构造函数的类,请通过应用System.Text.Json
属性来指定要使用的构造函数。当不使用该属性时,始终使用公共无参数构造函数(如果存在)。该属性只能与公共构造函数一起使用。[JsonConstructor]
...
参数化构造函数的参数名称必须与属性名称和类型匹配。匹配不区分大小写,即使使用
重命名属性,构造函数参数也必须与实际属性名称匹配。 [1][JsonPropertyName]
您的第一个问题是类型
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 这里。
要解决此问题,您必须:
添加一个公共无参数构造函数并使
Repository
可变(即为 GitDirectory
添加一个 setter),或者
添加一个构造函数,其参数的类型和名称与属性
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 年更新。在提出这个问题时,文档仅说明了
参数化构造函数的参数名称必须与属性名称匹配。
就我而言,我是从抽象类继承的。这个类有一个空的构造函数,我的子类也是如此。我什至为我传递给控制器的数据对象类尝试了自定义 JsonConverter,该控制器使用 [FromBody] 注释作为参数接收它。
那没有帮助..我的问题是我的抽象类没有 { get;放; } 在其公共属性上。我使用参数化构造函数创建了此类,以避免它成为贫乏的域模型,但认为它不需要 getter 和 setter。确保 JSON 属性名称(如果有属性)也匹配。我在这个上花了几个小时!