在运行时将 JSON 序列化和反序列化为类型对象,而不存储类型信息

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

尝试创建一个相当简单的发件箱模式,而不依赖于外部库。我想要完成的核心事情是处理暂时性故障。我使用 CQRS 和 Mediatr 来保持视图数据库与事务数据库的最终一致性。我通过处理程序通过进程内事件更新视图数据库。 (例如 UserCreatedEvent -> UserCreatedEventHandler)

为了确保事件不会因瞬时故障而丢失,我将它们存储在数据库中并通过后台进程发布它们。由于处理程序被键入到一个类中,因此只会执行一个处理程序(UserCreatedEvent 由 UserCreatedEventHandler 处理)。

我可以在一个项目中定义 X 个事件类型,并且它们在属性方面都是相互独立的。它们都有一个通用的标记界面(INotification)。为了处理瞬时故障,在保存事务数据库时,我序列化事件并存储在数据库中,如下所示。

public record SampleMessage(string Value, string Type);

//serialization
string typeOfNotification = typeof(T).AssemblyQualifiedName;
SampleMessage message = new(JsonSerializer.Serialize(content), typeOfNotification);

//deserialization
Type messageType = Type.GetType(message.Type)
var dvalue = JsonSerializer.Deserialize(message.Value, messageType);
await publisher.Publish(dvalue);

我非常关心在对象中存储类型信息。我最终可能会从将它们存储在数据库中转向将它们发布到代理并通过外部系统进行处理。我相信由于存在漏洞,也不再推荐存储类型。什么是可维护的良好替代方案?

我的一个想法是存储一个“Type”枚举,它只存储与事件相关的内容(例如 UserCreated)

public record SampleMessage(string Value, MessageType Type);
public MessageType { UserCreated, UserDeleted };

//deserialize
var result = switch content.Type
{
  UserCreated=> JsonSerializer.Deserialize<UserCreatedEvent>(content)
   _ => //Throw exception
 }

这相当简单,但我最终会得到大量的 switch 语句,并在每次添加新事件时修改它。我看到的与此相关的大多数答案都是使用自定义转换器,但它们主要用于多态反序列化(例如,使用 json.net 在没有类型信息的情况下反序列化多态 json 类)。我需要反序列化为没有任何继承的非多态类型对象。

这方面的最佳实践是什么?

c# json
1个回答
0
投票
您关心存储类型并在反序列化期间使用它们是正确的,因为这可能会造成非常严重的漏洞。

dotnet 的原生 JSON 库有一个有趣的方法来解决这个问题,我想说它也可以用于您的情况。

以这个基于

System.Text.Json

 版本 7+ 和 dotnet 8 的示例为例:

using System.Text.Json.Serialization; [JsonDerivedType(typeof(Cat), "cat")] [JsonDerivedType(typeof(Dog), "dog")] class Animal { public string Name { get; set; } } class Cat : Animal; class Dog : Animal;
序列化器在生成的 JSON 中使用 

$type

 属性,读取为“cat”或“dog”,并且在反序列化 JSON 时,它可以安全地依赖 
$type
 将 JSON 映射到目标类型,因为它是一个为该类型明确定义的鉴别器名称,并且它不能用于恶意加载目标类型来代替 
Cat
Dog
 次。

现在,以该示例为例,由于您提到您的结构中已经有

INotification

 标记,因此您也许可以直接利用 
System.Text.Json
 并解决漏洞。

但是,如果您的设置不允许您直接执行此操作,您将能够自己实现类似的解决方案。

    您可以创建一个自定义属性,假设
  1. NotificationDiscriminatorAttribute
     接受鉴别器名称。
  2. 您将使用此属性标记您的通知类型,并为其指定唯一的鉴别器名称。
  3. 启动时,使用反射找到所有标有此属性的类型,并在内存中保留鉴别器名称及其对应类型的字典。
  4. 序列化消息时,添加额外的属性,如
  5. $type
    $discriminator
     来存储使用反射提取的类型的鉴别器名称。
  6. 继续使用与反序列化 (
  7. JsonSerializer.Deserialize(message.Value, messageType);
    ) 消息相同的方法,但这次使用 
    $type
    $discriminator
     从您在步骤 3 中创建的字典中提取目标类型以进行反序列化。 
使用此方法,即使您使用完整类型名称作为鉴别器名称(我建议不要这样做),您仍然使用一组预定义的类型进行反序列化,这将防止反序列化时对其他类型的任何恶意调用。

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