由于对象(System.Text.Json)的当前状态,因此该操作无效 [英] Operation is not valid due to the current state of the object (System.Text.Json)

查看:167
本文介绍了由于对象(System.Text.Json)的当前状态,因此该操作无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个API,该API仅将传入的JSON文档发布到消息总线上,并且为每个文档分配了GUID.我们将从.Net Core 2.2升级到3.1,并打算用新的System.Text.Json库替换NewtonSoft.

We've got an API, which simply posts incoming JSON documents to a message bus, having assigned a GUID to each. We're upgrading from .Net Core 2.2 to 3.1 and were aiming to replace NewtonSoft with the new System.Text.Json library.

我们反序列化传入的文档,将GUID分配给其中一个字段,然后重新序列化,然后再发送到消息总线.不幸的是,重新序列化失败,并出现Operation is not valid due to the current state of the object例外.

We deserialise the incoming document, assign the GUID to one of the fields and then reserialise before sending to the message bus. Unfortunately, the reserialisation is failing with the exception Operation is not valid due to the current state of the object.

这是显示问题的控制器:-

Here's a controller that shows the problem:-

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对象的定义如下:-

The JsonApiMessage object is defined like this:-

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; }
    }
}

一个示例调用如下所示:-

An example call looks like this:-

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"
    }
  }
}

当我在Visual Studio的一个断点处检查payload的内容时,它在顶层看起来不错,但是JsonElement位看起来是不透明的,所以我不知道它们是否已正确解析.它们的结构可以变化,因此我们只关心它们是有效的JSON.在旧的NewtonSoft版本中,它们是JObject.

When I examine the contents of payload at a breakpoint in Visual Studio, it looks OK at the top level but the JsonElement bits look opaque, so I don't know if they've been parsed properly. Their structure can vary, so we only care that they are valid JSON. In the old NewtonSoft version, they were JObjects.

添加GUID后,在断点处检查时,它会出现在payload对象中,但我怀疑该问题与对象中的其他元素为只读或类似内容有关.

After the GUID has been added, it appears in the payload object when examined at a breakpoint but I'm suspicious that the problem is related to other elements in the object being read-only or something similar.

推荐答案

可以使用以下更简单的示例重现您的问题.定义以下模型:

Your problem can be reproduced with the following more minimal example. Define the following model:

public class JsonApiMessage
{
    public JsonElement data { get; set; }
}

然后尝试反序列化并重新序列化一个空的JSON对象,如下所示:

Then attempt to deserialize and re-serialize an empty JSON object like so:

var payload = JsonSerializer.Deserialize<JsonApiMessage>("{}");
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });

您将得到一个例外(演示小提琴#1 此处):

And you will get an exception (demo fiddle #1 here):

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)

问题似乎是此处). (这与JObject相反,后者是引用类型,其默认值当然是null.)

The problem seems to be that JsonElement is a struct, and the default value for this struct can't be serialized. In fact, simply doing JsonSerializer.Serialize(new JsonElement()); throws the same exception (demo fiddle #2 here). (This contrasts with JObject which is a reference type whose default value is, of course, null.)

那么,您有什么选择?您可以将所有JsonElement属性设置为可为空,并设置

So, what are your options? You could make all your JsonElement properties be nullable, and set IgnoreNullValues = true while re-serializing:

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 此处.

或者,您可以通过将除Id以外的所有JSON属性绑定到

Or, you could simplify your data model by binding all the JSON properties other than Id to a JsonExtensionData property like so:

public class JsonApiData
{
    [JsonPropertyName("id")]
    public string Id { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> ExtensionData { get; set; }
}

这种方法避免了在重新序列化时需要手动设置IgnoreNullValues的情况,因此ASP.NET Core将自动正确地对模型进行序列化.

This approach avoids the need to manually set IgnoreNullValues when re-serializing, and thus ASP.NET Core will re-serialize the model correctly automatically.

演示小提琴#4 此处.

这篇关于由于对象(System.Text.Json)的当前状态,因此该操作无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆