如何序列化泛型对象

How to serialize a generic object

提问人:Zenith 提问时间:9/6/2023 最后编辑:Uwe KeimZenith 更新时间:9/18/2023 访问量:195

问:

我很抱歉这个糟糕的标题,但我真的不知道如何以其他方式表达。具体问题如下:

我有一个带有控制器的 .NET API。我有一个控制器,它引用了泛型 Entity 类型的服务,并返回一个泛型 的 ,如下所示:IEnumerableDto<>

public class SomeController {
private readonly Service<SomeEntity> _someService;

    //constructor
    
    [HttpGet]
    public async Task<IEnumerable<Dto<SomeEntity>>> AsyncGetSomeData() {
        return await _someService.AsyncGet();
    }

}

服务接口如下所示;它的目的是调用实体的存储库,并将实体转换为 DTO,其方式可以用于给定的:DtoEntity

public interface Service<T> where T : Entity
{
    Task<IEnumerable<Dto<T>>?> AsyncGet();
}

实体界面如下所示(目前):

public interface Entity {}

Dto 界面如下所示:

public interface Dto<T> where T : Entity.Entity {}

Entity 接口的实现如下所示:

public record SomeEntity (Guid id) : Entity

Dto 的实现如下所示:

public record SomeDto(string Id) : Dto<SomeEntity>;

当我以现在设置控制器的方式返回对象时(如上所述),将返回空的 json 对象。这是因为 json 序列化程序无法确定可以序列化为 .更改要返回的签名并将项目强制转换为实际上可以正确序列化对象,这一事实证明了这一点。Dto<SomeEntity>SomeDtoIEnumerable<SomeDto>IEnumerable<Dto<SomeEntity>>SomeDto

我的问题是:是否可以更改设置或映射或其他内容以避免更改签名和投射?Dto<SomeEntity>SomeDto

我尝试过找到这样的例子(Google 和 StackOverflow),但正如你所看到的,这个问题用一句话来概括是相当复杂的(我正在接受标题建议来改进这个问题)。我希望有人能给我指出一个重复的问题,或者给我一个答案。

编辑: 我正在尝试使用 .我有两个实体类型和相应的 dto:JsonDerivedTypeAttribute

public sealed record CourseDto() : Dto<CourseEntity>

public sealed record CourseDefinitionDto() : Dto<CourseDefinitionEntity>

然后,我在接口上定义了:JsonDerivedTypeAttributeDto

[JsonDerivedType(derivedType: typeof(CourseDto), "a")]
[JsonDerivedType(derivedType: typeof(CourseDefinitionDto), "b")]
public interface Dto<T> where T : Entity.Entity;

无论我在 typeDiscriminator 参数中使用什么,我都会收到以下错误:

“System.InvalidOperationException:指定类型'Application.Data.Dto.CourseDto'不是多态类型'Application.Data.Dto.Dto'1[Application.Data.Entity.CourseDefinitionEntity]'支持的派生类型。派生类型必须可分配给基类型,不能是泛型的,并且不能是 abstact 类或接口,除非指定了“JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor”。

C# JSON .NET 序列化 system.text.json

评论

1赞 Panagiotis Kanavos 9/6/2023
所有序列化程序都是通用的。问题在于,您希望从中选取无限可能的类型之一,而无需实际指定它。你为什么要这样做?这看起来像是试图过度概括一些实际上非常具体的东西——Dto<SomeEntity>SomeDto
0赞 Zenith 9/6/2023
一个实体可能有多种类型的 dto。使用的那个取决于它所处的环境。因此,服务需要能够返回泛型dto<SomeEntity>

答:

1赞 Mad hatter 9/6/2023 #1

如果您使用的是 System.Text.Json,则应查看 JsonDerivedTypeAttributeJsonConverterAttribute

以下是您可以执行的操作的快速示例JsonDerivedTypeAttribute

public interface Entity { }

[JsonDerivedType(typeof(SomeDto), "#some")]
[JsonDerivedType(typeof(AnotherDto), "#another")]
public interface Dto { }

public interface Dto<T> : Dto where T : Entity { }

public class SomeEntity : Entity { }

public class AnotherEntity : Entity { }

public class SomeDto : Dto<SomeEntity> { }

public class AnotherDto : Dto<AnotherEntity> { }

要使用的示例

Dto some = new SomeDto();
Dto another = new AnotherDto();

var options = new JsonSerializerOptions();
var someJson = JsonSerializer.Serialize(some, options);
var anotherJson = JsonSerializer.Serialize(another, options);

Console.WriteLine(someJson);
Console.WriteLine(anotherJson);

var someDto = JsonSerializer.Deserialize<Dto>(someJson);
var anotherDto = JsonSerializer.Deserialize<Dto>(anotherJson);

Console.WriteLine(someDto?.GetType().Name);
Console.WriteLine(anotherDto?.GetType().Name);

输出

{"$type":"#some"}
{"$type":"#another"}
SomeDto
AnotherDto

编辑

下面是一个提示,让你的控制器能够返回接口,而不是泛型接口。 你可以这样做,因为接口是协变的。DtoDto<T>IEnumerable<T>

public interface Service<T> where T : Entity
{
    Task<IEnumerable<Dto<T>>?> GetAsync();
}

您的操作签名变为

[HttpGet]
public async Task<IEnumerable<Dto>?> GetSomeDataAsync()
{
    return await _someService.GetAsync();
}

我切换了方法名称中的单词,以更符合 C# 命名约定。Async

评论

1赞 Zenith 9/6/2023
JsonDerivedType 属性似乎有效。需要升级到 .NET 7,但没关系。很遗憾没有办法在程序.cs文件中映射这些东西,因为现在我的Dto接口将是巨大的
1赞 Mad hatter 9/12/2023
您应该发布更新后的代码,其中包含使用的转换器和属性。没有它,我无法猜测出了什么问题。
1赞 Mad hatter 9/12/2023
我添加了一个工作示例 - 希望它有所帮助JsonDerivedTypeAttribute
1赞 Mad hatter 9/18/2023
无需删除泛型接口或实现。只需添加一个非通用的 inferface,即可处理所有带有修饰的类型。Dto<T>DtoJsonDerivedTypeAttribute
1赞 Mad hatter 9/18/2023
Dto<T>不能装饰,因为它是通用的。我做了一个编辑来简化代码。如您所见,只有操作签名发生了变化。使用非泛型接口欺骗序列化程序。JsonDervivedTypeAttributeDto
1赞 Thomas Koelle 9/14/2023 #2

我的建议是你给它两个类:

public class MyClass<Tin,Tout>

评论

1赞 Mad hatter 9/14/2023
对于我假设的类,有方法签名吗?这也将起作用,并将抑制任何通用序列化问题。ServiceTask<IEnumerable<Tout>?> AsyncGet();
0赞 Thomas Koelle 9/14/2023
确切地说,这将是方法签名
0赞 Mad hatter 9/14/2023
不过,这可能与以下要求相冲突:“一个实体可能有多种类型的 dto。使用的那个取决于它所处的环境。我猜在运行时以某种方式注入了变量。目前还不是很清楚。_someService
0赞 Thomas Koelle 9/14/2023
是的,那么有时您必须执行 var x = MyClass<C,C1>() 和其他时候 var x = MyClass<C,C2>,但无论如何都必须在一个地方或另一个地方进行配置。
0赞 Mad hatter 9/14/2023
但是你会如何处理动作签名呢?仅在运行时知道 C1 或 C2 ?AsyncGetSomeData