ASP.NET Core - System.Text.Json:如何拒绝有效负载中的未知属性?

2023-12-21

ASP.NET Core 7 中的 Web API 与 System.Text.Json:

我需要拒绝 PUT/POST API 上的 JSON 有效负载,这些 API 指定了其他属性,这些属性不映射到模型中的任何属性。

所以如果我的模型是

public class Person {
  public string Name { get; set; }
}

我需要拒绝任何如下所示的有效负载(带有 400-Bad Request 错误)

{
  "name": "alice",
  "lastname": "bob"
}

如何才能实现这一目标?


目前 System.Text.Json 没有与 Json.NET 等效的选项MissingMemberHandling.Error https://www.newtonsoft.com/json/help/html/DeserializeMissingMemberHandling.htm当反序列化的 JSON 具有未映射的属性时强制发生错误的功能。如需确认,请参阅:

  • 将 Newtonsoft.Json 与 System.Text.Json 进行比较,并迁移到 System.Text.Json https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-7-0#missingmemberhandling:

    如果 JSON 包含目标类型中缺少的属性,则可以将 Newtonsoft.Json 配置为在反序列化期间引发异常。 System.Text.Json 会忽略 JSON 中的额外属性,除非您使用[JsonExtensionData] 属性 https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/handle-overflow。对于缺少的成员功能没有解决方法。

  • 添加对 MissingMemberHandling 的支持到 System.Text.Json #37483 https://github.com/dotnet/runtime/issues/37483.

但是,即使官方文档指出缺少成员功能没有解决方法,您也可以使用[JsonExtensionData]要模拟的属性MissingMemberHandling.Error.

首先,如果您只想实现几种类型MissingMemberHandling.Error,您可以添加一个扩展数据字典,然后检查它是否包含内容并在JsonOnDeserialized.OnDeserialized() https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.ijsonondeserialized?view=net-7.0回调,或者按照建议在您的控制器中这个答案 https://stackoverflow.com/a/74995893/3744182 by 迈克尔·刘 https://stackoverflow.com/users/1127114/michael-liu.

其次,如果您需要实施MissingMemberHandling.Error对于每种类型,在 .NET 7 及更高版本中,您可以添加DefaultJsonTypeInfoResolver 修饰符 https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/custom-contracts#modifiers添加一个合成扩展数据属性,该属性会在未知属性上引发错误。

为此,请定义以下扩展方法:

public static class JsonExtensions
{
    public static DefaultJsonTypeInfoResolver AddMissingMemberHandlingError(this DefaultJsonTypeInfoResolver resolver)
    {
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeInfo.Kind != JsonTypeInfoKind.Object)
                                       return;
                                   if (typeInfo.Properties.Any(p => p.IsExtensionData))
                                       return;
                                   var property = typeInfo.CreateJsonPropertyInfo(typeof(Dictionary<string, JsonElement>), "<>ExtensionData");
                                   property.IsExtensionData = true;
                                   property.Get = static (obj) => null;
                                   property.Set = static (obj, val) => 
                                   {
                                       var dictionary = (Dictionary<string, JsonElement>?)val;
                                       Console.WriteLine(dictionary?.Count);
                                       if (dictionary != null)
                                           throw new JsonException();
                                   };
                                   typeInfo.Properties.Add(property);
                               });
        return resolver;
    }
}

然后按如下方式配置您的选项:

var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        .AddMissingMemberHandlingError(),
};

这样做之后,一个JsonException当遇到缺少的 JSON 属性时将抛出该异常。但请注意,Systen.Text.Json 在填充之前设置分配的字典,因此在使用此解决方法时,您将无法在异常消息中包含缺少的成员名称。

演示小提琴here https://dotnetfiddle.net/TiyGaM.

Update

如果您需要实施MissingMemberHandling.Error对于每种类型and also需要异常错误消息包含未知属性的名称,可以通过定义自定义字典类型来完成,每当尝试向字典添加任何内容时,该类型都会引发自定义异常。然后使用该自定义字典类型作为合约修饰符添加的合成扩展属性中的扩展字典类型,如下所示:

// A JsonException subclass that allows for a custom message that includes the path, line number and byte position.
public class JsonMissingMemberException : JsonException
{
    readonly string? innerMessage;
    public JsonMissingMemberException() : this(null) { }
    public JsonMissingMemberException(string? innerMessage) : base(innerMessage) => this.innerMessage = innerMessage;
    public JsonMissingMemberException(string? innerMessage, Exception? innerException) : base(innerMessage, innerException) => this.innerMessage = innerMessage;
    protected JsonMissingMemberException(SerializationInfo info, StreamingContext context) : base(info, context) => this.innerMessage = (string?)info.GetValue("innerMessage", typeof(string));
    public override string Message =>
        innerMessage == null
            ? base.Message
            : String.Format("{0} Path: {1} | LineNumber: {2} | BytePositionInLine: {3}.", innerMessage, Path, LineNumber, BytePositionInLine);
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("innerMessage", innerMessage);
    }
}

public static class JsonExtensions
{
    class UnknownPropertyDictionary<TModel> : IDictionary<string, JsonElement>
    {       
        static JsonException CreateException(string key, JsonElement value) =>
            new JsonMissingMemberException(String.Format("Unexpected property \"{0}\" encountered while deserializing type {1}.", key, typeof(TModel).FullName));
        
        public void Add(string key, JsonElement value) => throw CreateException(key, value);
        public bool ContainsKey(string key) => false;
        public ICollection<string> Keys => Array.Empty<string>();
        public bool Remove(string key) => false; 
                                    
        public bool TryGetValue(string key, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out JsonElement value) { value = default; return false; }
        public ICollection<JsonElement> Values => Array.Empty<JsonElement>();
        public JsonElement this[string key]
        {
            get => throw new KeyNotFoundException(key);
            set =>  throw CreateException(key, value);
        }
        public void Add(KeyValuePair<string, JsonElement> item) =>  throw CreateException(item.Key, item.Value);
        public void Clear() => throw new NotImplementedException();
        public bool Contains(KeyValuePair<string, JsonElement> item) => false;
        public void CopyTo(KeyValuePair<string, JsonElement>[] array, int arrayIndex) { }
        public int Count => 0;
        public bool IsReadOnly => false;
        public bool Remove(KeyValuePair<string, JsonElement> item) => false;
        public IEnumerator<KeyValuePair<string, JsonElement>> GetEnumerator() => Enumerable.Empty<KeyValuePair<string, JsonElement>>().GetEnumerator();
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
    }

    public static DefaultJsonTypeInfoResolver AddMissingMemberHandlingError(this DefaultJsonTypeInfoResolver resolver)
    {
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeInfo.Kind != JsonTypeInfoKind.Object)
                                       return;
                                   if (typeInfo.Properties.Any(p => p.IsExtensionData))
                                       return;
                                   var dictionaryType = typeof(UnknownPropertyDictionary<>).MakeGenericType(typeInfo.Type);
                                   JsonPropertyInfo property = typeInfo.CreateJsonPropertyInfo(dictionaryType, "<>ExtensionData");
                                   property.IsExtensionData = true;
                                   property.Get = (obj) => Activator.CreateInstance(dictionaryType);
                                   property.Set = static (obj, val) => { };
                                   typeInfo.Properties.Add(property);
                               });
        return resolver;
    }
}

然后,如果我尝试将具有未知属性的 JSON 反序列化为不包含该属性的模型,则会引发以下异常:

JsonMissingMemberException: Unexpected property "Unknown" encountered while deserializing type Model. Path: $.Unknown | LineNumber: 6 | BytePositionInLine: 16.
   at JsonExtensions.UnknownPropertyDictionary`1.set_Item(String key, JsonElement value)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.ReadJsonAndAddExtensionProperty(Object obj, ReadStack& state, Utf8JsonReader& reader)

Notes:

  • 的自定义子类JsonException需要包含自定义消息以及路径、行号和字节位置。

  • 异常消息中仅包含第一个未知属性的名称。

Demo #2 here https://dotnetfiddle.net/DbmJfT.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ASP.NET Core - System.Text.Json:如何拒绝有效负载中的未知属性? 的相关文章

  • 当我使用“control-c”关闭发送对等方的套接字时,为什么接收对等方的套接字不断接收“”

    我是套接字编程的新手 我知道使用 control c 关闭套接字是一个坏习惯 但是为什么在我使用 control c 关闭发送进程后 接收方上的套接字不断接收 在 control c 退出进程后 发送方的套接字不应该关闭吗 谢谢 我知道使用
  • pthread_cond_timedwait() 和 pthread_cond_broadcast() 解释

    因此 我在堆栈溢出和其他资源上进行了大量搜索 但我无法理解有关上述函数的一些内容 具体来说 1 当pthread cond timedwait 因为定时器值用完而返回时 它如何自动重新获取互斥锁 互斥锁可能被锁定在其他地方 例如 在生产者
  • UML类图:抽象方法和属性是这样写的吗?

    当我第一次为一个小型 C 项目创建 uml 类图时 我在属性方面遇到了一些麻烦 最后我只是将属性添加为变量 lt
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 为什么#pragma optimize("", off)

    我正在审查一个 C MFC 项目 在某些文件的开头有这样一行 pragma optimize off 我知道这会关闭所有以下功能的优化 但这样做的动机通常是什么 我专门使用它来在一组特定代码中获得更好的调试信息 并在优化的情况下编译应用程序
  • 获取没有非标准端口的原始 url (C#)

    第一个问题 环境 MVC C AppHarbor Problem 我正在调用 openid 提供商 并根据域生成绝对回调 url 在我的本地机器上 如果我点击的话 效果很好http localhost 12345 login Request
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 使用 System.Text.Json 即时格式化 JSON 流

    我有一个未缩进的 Json 字符串 例如 hash 123 id 456 我想缩进字符串并将其序列化为 JSON 文件 天真地 我可以使用缩进字符串Newtonsoft如下 using Newtonsoft Json Linq JToken
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • C++ 复制初始化和直接初始化,奇怪的情况

    在继续阅读本文之前 请阅读在 C 中 复制初始化和直接初始化之间有区别吗 https stackoverflow com questions 1051379 is there a difference in c between copy i
  • 控制到达非 void 函数末尾 -wreturn-type

    这是查找四个数字中的最大值的代码 include
  • 如何让Gtk+窗口背景透明?

    我想让 Gtk 窗口的背景透明 以便只有窗口中的小部件可见 我找到了一些教程 http mikehearn wordpress com 2006 03 26 gtk windows with alpha channels https web
  • C - 直接从键盘缓冲区读取

    这是C语言中的一个问题 如何直接读取键盘缓冲区中的数据 我想直接访问数据并将其存储在变量中 变量应该是什么数据类型 我需要它用于我们研究所目前正在开发的操作系统 它被称为 ICS OS 我不太清楚具体细节 它在 x86 32 位机器上运行
  • 为什么 C# Math.Ceiling 向下舍入?

    我今天过得很艰难 但有些事情不太对劲 在我的 C 代码中 我有这样的内容 Math Ceiling decimal this TotalRecordCount this PageSize Where int TotalRecordCount
  • 为什么我收到“找不到编译动态表达式所需的一种或多种类型。”?

    我有一个已更新的项目 NET 3 5 MVC v2 到 NET 4 0 MVC v3 当我尝试使用或设置时编译出现错误 ViewBag Title财产 找不到编译动态表达式所需的一种或多种类型 您是否缺少对 Microsoft CSharp
  • const、span 和迭代器的问题

    我尝试编写一个按索引迭代容器的迭代器 AIt and a const It两者都允许更改容器的内容 AConst it and a const Const it两者都禁止更改容器的内容 之后 我尝试写一个span
  • Validation.ErrorTemplate 的 Wpf 动态资源查找

    在我的 App xaml 中 我定义了一个资源Validation ErrorTemplate 这取决于动态BorderBrush资源 我打算定义独特的BorderBrush在我拥有的每个窗口以及窗口内的不同块内
  • C 中的异或运算符

    在进行按位操作时 我在确定何时使用 XOR 运算符时遇到一些困难 按位与和或非常简单 当您想要屏蔽位时 请使用按位 AND 常见用例是 IP 寻址和子网掩码 当您想要打开位时 请使用包含或 然而 XOR 总是让我明白 我觉得如果在面试中被问
  • 限制C#中的并行线程数

    我正在编写一个 C 程序来生成并通过 FTP 上传 50 万个文件 我想并行处理4个文件 因为机器有4个核心 文件生成需要更长的时间 是否可以将以下 Powershell 示例转换为 C 或者是否有更好的框架 例如 C 中的 Actor 框
  • 恢复上传文件控制

    我确实阅读了以下帖子 C 暂停 恢复上传 https stackoverflow com questions 1048330 pause resume upload in c 使用 HTTP 恢复上传 https stackoverflow

随机推荐