c# - 由于对象的当前状态(System.Text.Json),操作无效
问题描述
我们有一个 API,它只是将传入的 JSON 文档发布到消息总线,并为每个文档分配一个 GUID。我们正在从 .Net Core 2.2 升级到 3.1,并打算用新System.Text.Json
库替换 NewtonSoft。
我们反序列化传入的文档,将 GUID 分配给其中一个字段,然后在发送到消息总线之前重新序列化。不幸的是,重新序列化失败并出现异常Operation is not valid due to the current state of the object
。
这是一个显示问题的控制器:-
using System;
using System.Net;
using Project.Models;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Text.Json;
namespace Project.Controllers
{
[Route("api/test")]
public class TestController : Controller
{
private const string JSONAPIMIMETYPE = "application/vnd.api+json";
public TestController()
{
}
[HttpPost("{eventType}")]
public async System.Threading.Tasks.Task<IActionResult> ProcessEventAsync([FromRoute] string eventType)
{
try
{
JsonApiMessage payload;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) {
string payloadString = await reader.ReadToEndAsync();
try {
payload = JsonSerializer.Deserialize<JsonApiMessage>(payloadString);
}
catch (Exception ex) {
return StatusCode((int)HttpStatusCode.BadRequest);
}
}
if ( ! Request.ContentType.Contains(JSONAPIMIMETYPE) )
{
return StatusCode((int)HttpStatusCode.UnsupportedMediaType);
}
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
// we would send the message here but for this test, just reserialise it
string reserialisedPayload = JsonSerializer.Serialize(payload);
Request.HttpContext.Response.ContentType = JSONAPIMIMETYPE;
return Accepted(payload);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
JsonApiMessage 对象的定义如下:-
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Project.Models
{
public class JsonApiMessage
{
[JsonPropertyName("data")]
public JsonApiData Data { get; set; }
[JsonPropertyName("included")]
public JsonApiData[] Included { get; set; }
}
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement Relationships { get; set; }
}
}
一个示例调用如下所示:-
POST http://localhost:5000/api/test/event
Content-Type: application/vnd.api+json; charset=UTF-8
{
"data": {
"type": "test",
"attributes": {
"source": "postman",
"instance": "jg",
"level": "INFO",
"message": "If this comes back with an ID, the API is probably working"
}
}
}
当我payload
在 Visual Studio 中检查断点处的内容时,它在顶层看起来不错,但这些JsonElement
位看起来不透明,所以我不知道它们是否已被正确解析。它们的结构可以变化,所以我们只关心它们是有效的 JSON。在旧的 NewtonSoft 版本中,它们是JObject
s。
添加 GUID 后,它会在断点检查时出现在payload
对象中,但我怀疑该问题与对象中的其他元素为只读或类似内容有关。
解决方案
您的问题可以通过以下更简单的示例重现。定义以下模型:
public class JsonApiMessage
{
public JsonElement data { get; set; }
}
然后尝试反序列化并重新序列化一个空的 JSON 对象,如下所示:
var payload = JsonSerializer.Deserialize<JsonApiMessage>("{}");
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
你会得到一个例外(这里是演示小提琴#1 ):
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Text.Json.JsonElement.WriteTo(Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.JsonConverterJsonElement.Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
问题似乎JsonElement
是 a struct
,并且该结构的默认值无法序列化。事实上,简单地做JsonSerializer.Serialize(new JsonElement());
会引发同样的异常(这里是演示小提琴#2 )。(这与JObject
which 是默认值当然是 . 的引用类型形成对比null
。)
那么,您有哪些选择?您可以使所有JsonElement
属性都可以为空,并IgnoreNullValues = true
在重新序列化时设置:
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement? Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
}
进而:
var reserialisedPayload = JsonSerializer.Serialize(payload, new JsonSerializerOptions { IgnoreNullValues = true });
演示小提琴#3在这里。
或者,在.NET 5 或更高版本中,您可以JsonElement
使用以下标记所有属性[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
:
public class JsonApiData
{
// Remainder unchanged
[JsonPropertyName("attributes")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Relationships { get; set; }
}
这样做会导致在序列化期间跳过未初始化的元素,而无需修改序列化选项。
演示小提琴#4在这里。
或者,您可以通过绑定所有 JSON 属性来简化数据模型,而不是像这样Id
的JsonExtensionData
属性:
public class JsonApiData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
}
这种方式避免了重新序列化时需要手动设置IgnoreNullValues
,因此 ASP.NET Core 会自动正确地重新序列化模型。
演示小提琴#5在这里。
推荐阅读
- python - Azure Functions Python 状态:500 内部服务器错误
- php - 如何在 Mac OS X Catalina 中的 PHP 上加载 memcached?
- excel - 检测双打的公式,并选择具有最高 POP 数的公式
- c++ - 虚幻引擎 4 (ue4) 中带有自定义 c++ 蓝图函数库的蓝图循环/for/while 节点
- scala - 这是scala中的什么类型的表达?
- css - 输入字段,移动正在输入的文本
- c# - 中间件中断 web api 调用
- java - 如何将我的自定义数据类设为 StatefulRedisConnection 值类型?
- ios - 如何自动缩放mapView以显示覆盖
- sql - 在一个表中创建依赖于另一个表中的数据的条目