System.Text.Json:序列化数据时如何强制执行默认的 camelCase JsonNamingPolicy?

System.Text.Json: How can I enforce default camelCase JsonNamingPolicy when Serializing data?

提问人:Kounavi 提问时间:11/15/2023 更新时间:11/16/2023 访问量:72

问:

我有一个烦人的问题,当我添加自定义JSON转换器时,在反序列化属性时不遵守命名策略。无论如何,它始终默认为 PascalCase。

我还可以使用 .NET 6 使用最小的 Web API 复制它。

程序.cs


using System.Text.Json;
using Microsoft.AspNetCore.Http.Json;
using MinimalWebApi.Converters;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddMvc();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.Configure<JsonOptions>(
    options => {
        options.SerializerOptions.Converters.Add(new FooModelJsonConverter());
        //NOTE: This is the default policy but enabling it does not make a difference.
        //options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    }
);

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

FooModelJsonConverter.cs


using System.Text.Json;
using System.Text.Json.Serialization;
using MinimalWebApi.Model;

namespace MinimalWebApi.Converters;

public class FooModelJsonConverter: JsonConverter<FooModel>
{
    public override FooModel Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options
    )
    {

        return
            JsonSerializer.Deserialize<FooModel>(ref reader, options)
            ?? throw new JsonException("FooModel could not be deserialized as it was null!");

    }

    public override void Write(
        Utf8JsonWriter writer,
        FooModel fooModel,
        JsonSerializerOptions options
    ) {

        writer.WriteStartObject();

        var majorVersion = fooModel.MajorVersion;
        var minorVersion = fooModel.MinorVersion;

        if (majorVersion == null || minorVersion == null)
            throw new JsonException(
                $"Expected a 'Major' & 'Minor' attribute but one (or both) of them is null => MajorVersion: {majorVersion}\nMinorVersion: {minorVersion}!"
            );

        switch (majorVersion)
        {
            case 1:
                writer.WritePropertyName(TryParseCamelCase(nameof(fooModel.Id), options, true));
                JsonSerializer.Serialize(writer, fooModel.Id, options);
                switch (minorVersion)
                {
                    case 0:
                        break;
                    case 1:
                        break;
                    case 2:
                        break;
                    case 3:
                        writer.WritePropertyName(TryParseCamelCase(nameof(fooModel.AnotherId), options, true));
                        JsonSerializer.Serialize(writer, fooModel.AnotherId, options);
                        break;
                    default:
                        throw new JsonException($"Unexpected 'Minor' version attribute: {minorVersion}");
                }
                writer.WritePropertyName(TryParseCamelCase(nameof(fooModel.Name), options, true));
                JsonSerializer.Serialize(writer, fooModel.Name, options);
                writer.WritePropertyName(TryParseCamelCase(nameof(fooModel.Status), options, true));
                JsonSerializer.Serialize(writer, fooModel.Status, options);
                break;
            default:
                throw new JsonException($"Unexpected 'Major' version attribute: {majorVersion}");
        }

        writer.WriteEndObject();

    }

    public static string TryParseCamelCase(string propertyName, JsonSerializerOptions options, bool disabled = false)
    {

        var namingPolicy = options.PropertyNamingPolicy;

        if (!disabled)
            return namingPolicy != null ? namingPolicy.ConvertName(propertyName) : propertyName;
        else
            return propertyName;

    }

}

FooModel.cs


using System.Text.Json.Serialization;
using MinimalWebApi.Converters;

namespace MinimalWebApi.Model
{
    [JsonConverter(typeof(FooModelJsonConverter))]
    public class FooModel
    {

        public enum State { STARTED, RUNNING, COMPLETED, FAILED };

        public Guid Id { get; set; }
        
        public string? AnotherId { get; set; }
        public string? Name { get; set; }

        [JsonConverter(typeof(JsonStringEnumConverter))]
        public State Status { get; set; }

        [JsonIgnore]
        public int? MajorVersion { get; set; } = 1;
        
        [JsonIgnore]
        public int? MinorVersion { get; set; } = 0;

    }


}

如您所见,我找到了一种解决方法,即从 .但是,我想知道如何强制我的控制器正确序列化它? 请注意,我已尝试在添加服务时显式指定 CamelCase 选项(请参阅上面的代码;Program.cs),在我的自定义中创建一个新实例,并在我的控制器中指定一个显式,但这些不起作用。有人对此有什么想法吗?ConvertNamePropertyNamingPolicyJsonSerializerOptionsFooModelJsonConverterActionResult

PS:这篇 SO 帖子声称我们应该使用而不是在下面的响应/评论中进一步建议配置它们,因为 Swashbuckle 将无法正确解析 JSON 响应正文。但是,我怀疑这是否正确和/或有意义。using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Http.Json;

C# asp.net JSON System.Text.json

评论

0赞 Rush 11/15/2023
您是否尝试过在进行 http 调用的地方创建新的 Converter,而不是在 Converter 类中设置 CamelCase 选项?我猜你想做这样的事情吗?JsonSerializerOptionsJsonSerializer.Serialize(payload, options)
0赞 Kounavi 11/15/2023
我尝试过在转换器内外执行此操作,但是在转换器之外这样做是没有意义的,因为这样我们首先否定了使用中间件的所有积极影响,用各种(反)序列化污染代码,我们不想要的显式调用。另外,其他一切都在转换器内工作,或者如果我们在执行 DI 时更改任何其他 JSON 选项,因此我认为没有理由不将其视为设置。
0赞 dbc 11/15/2023
你写道,我有一个烦人的问题,当我添加自定义 JSON 转换器时,在反序列化属性时不遵守命名策略。 -- 但是如果尝试运行您的代码,将抛出 StackOverflowException,请参阅 dotnetfiddle.net/XGrlwf。问题在于您直接应用于模型,这意味着对反序列化的递归调用将递归调用转换器。你能告诉你分享一些导致问题的实际反序列化代码吗?JsonSerializer.Deserialize<FooModel>(jsonString, options);[JsonConverter(typeof(FooModelJsonConverter))]
0赞 Vyacheslav Benedichuk 11/15/2023
你能改写一下你的问题吗?您说“在反序列化属性时不遵守命名策略。',但你指的是与序列化相关的代码。在用于获取字段名称的 Write 方法中,它按预期返回 Pascal Case。您正在将此字符串传递给 with,导致按原样返回它。你想实现吗?nameof(fooModel.Id)TryParseCamelCasedisabled == true
0赞 Kounavi 11/16/2023
@dbc 我不认为 dotnetfiddle 可以理解中间件。如果你在 VS 项目中运行它,它可以很好地工作:)此外,注释应直接应用于模型。MS 文档:有关详细信息,请参阅 learn.microsoft.com/en-us/dotnet/standard/serialization/...。我遵循了这个例子,它在我的情况下就像一个魅力:)

答:

0赞 Kounavi 11/16/2023 #1

根据 GH-31818 问题:JsonConverter 中的 WritePropertyName 不考虑大小写,这是预期行为,因此我必须从相应的 JsonNamingPolicy 中显式调用 ConvertName。