18

我设法使用以下代码使其工作:

.AddNewtonsoftJson(options => {
    options.SerializerSettings.ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    };
});

然而,这使得 MVC 使用Newtonsoft而不是System.Text.JSON更快、异步和内置。

查看System.Text.JSON我只能找到 CamelCase 中的命名策略选项。是否有对蛇盒的本机支持?实现蛇案例 JSON 命名风格的更好方法是什么?

4

6 回答 6

20

只需对pfx代码稍作修改即可消除对Newtonsoft Json.Net.

String将给定字符串转换为SnakeCase.

public static class StringUtils
{
    public static string ToSnakeCase(this string str)
    {
        return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
    }
}

然后在我们的SnakeCaseNamingPolicy我们可以做

public class SnakeCaseNamingPolicy : JsonNamingPolicy
{
    public static SnakeCaseNamingPolicy Instance { get; } = new SnakeCaseNamingPolicy();

    public override string ConvertName(string name)
    {
        // Conversion to other naming convention goes here. Like SnakeCase, KebabCase etc.
        return name.ToSnakeCase();
    }
}

最后一步是将我们的命名策略注册到Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()            
        .AddJsonOptions(
            options => { 
                options.JsonSerializerOptions.PropertyNamingPolicy = 
                    SnakeCaseNamingPolicy.Instance;
            });
}

使用模型:

public class WeatherForecast
{
    public DateTime Date { get; set; }

    public int TemperatureCelcius { get; set; }

    public int TemperatureFahrenheit { get; set; }

    public string Summary { get; set; }
}

Json输出:

{
  "date": "2019-10-28T08:26:14.878444+05:00",
  "temperature_celcius": 4,
  "temperature_fahrenheit": 0,
  "summary": "Scorching"
}
于 2019-10-27T03:24:58.473 回答
15

目前没有对蛇案例的内置支持,
但允许通过继承.NET Core 3.0来设置自定义命名策略JsonNamingPolicy

您需要ConvertName使用蛇形大小写转换来实现该方法。
(Newtonsoft Json.NET 有一个内部StringUtils类,它显示了如何处理这个问题。)


下面的 POC 实现仅将 Json.NET 重新SnakeCaseNamingStrategy用于蛇形大小写转换(而整个应用程序使用System.Text.Json

最好避免依赖 Newtonsoft Json.Net 仅用于蛇形案例转换,但在下面这个相当懒惰的示例中,我不想重新思考/重新发明蛇形案例转换方法。
这个答案的要点是如何挂钩自定义策略(而不是蛇案例转换本身。)
(有许多库和代码示例展示了如何做到这一点。)

public class SnakeCaseNamingPolicy : JsonNamingPolicy
{
    private readonly SnakeCaseNamingStrategy _newtonsoftSnakeCaseNamingStrategy
        = new SnakeCaseNamingStrategy();

    public static SnakeCaseNamingPolicy Instance { get; } = new SnakeCaseNamingPolicy();

    public override string ConvertName(string name)
    { 
        /* A conversion to snake case implementation goes here. */

        return _newtonsoftSnakeCaseNamingStrategy.GetPropertyName(name, false);
    }
}

Startup.cs您应用此自定义SnakeCaseNamingPolicy

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()            
        .AddJsonOptions(
            options => { 
                options.JsonSerializerOptions.PropertyNamingPolicy = 
                    SnakeCaseNamingPolicy.Instance;
            });
}

下面的类的一个实例

public class WeatherForecast
{
    public DateTime Date { get; set; }

    public int TemperatureCelcius { get; set; }

    public int TemperatureFahrenheit { get; set; }

    [JsonPropertyName("Description")]
    public string Summary { get; set; }
}

Json表示为:

{ "date" : "2019-10-28T01:00:56.6498885+01:00",
  "temperature_celcius" : 48,
  "temperature_fahrenheit" : 118,
  "Description" : "Cool"
}

请注意,该属性已Summary被命名为 与其匹配的名称。Description
System.Text.Json.Serialization.JsonPropertyNameAttribute

于 2019-10-26T23:06:11.993 回答
2

我在这里分享了@pfx 解决方案的完整实现。自定义命名策略(从 NewtonSoft 复制):

using System.Text;
using System.Text.Json;

namespace Utils
{
    public class SnakeCaseNamingPolicy : JsonNamingPolicy
    {
        public override string ConvertName(string name) => JsonUtils.ToSnakeCase(name);
    }

    public class JsonUtils
    {

        private enum SeparatedCaseState
        {
            Start,
            Lower,
            Upper,
            NewWord
        }

        public static string ToSnakeCase(string s) => ToSeparatedCase(s, '_');
    
        private static string ToSeparatedCase(string s, char separator)
        {
            if (string.IsNullOrEmpty(s))
            {
                return s;
            }

            StringBuilder sb = new StringBuilder();
            SeparatedCaseState state = SeparatedCaseState.Start;

            for (int i = 0; i < s.Length; i++)
            {
                if (s[i] == ' ')
                {
                    if (state != SeparatedCaseState.Start)
                    {
                        state = SeparatedCaseState.NewWord;
                    }
                }
                else if (char.IsUpper(s[i]))
                {
                    switch (state)
                    {
                        case SeparatedCaseState.Upper:
                            bool hasNext = (i + 1 < s.Length);
                            if (i > 0 && hasNext)
                            {
                                char nextChar = s[i + 1];
                                if (!char.IsUpper(nextChar) && nextChar != separator)
                                {
                                    sb.Append(separator);
                                }
                            }
                            break;
                        case SeparatedCaseState.Lower:
                        case SeparatedCaseState.NewWord:
                            sb.Append(separator);
                            break;
                    }

                    char c;
                    c = char.ToLowerInvariant(s[i]);
                    sb.Append(c);

                    state = SeparatedCaseState.Upper;
                }
                else if (s[i] == separator)
                {
                    sb.Append(separator);
                    state = SeparatedCaseState.Start;
                }
                else
                {
                    if (state == SeparatedCaseState.NewWord)
                    {
                        sb.Append(separator);
                    }

                    sb.Append(s[i]);
                    state = SeparatedCaseState.Lower;
                }
            }

            return sb.ToString();
        }
    }
}

简单示例中使用了以下模型:

    public class TestSerializer
    {
        public DateTime TimeStamp { get; set; }
        public int CPUPower { get; set; } 
    }

以及示例用法:

var data = new TestSerializer();
data.TimeStamp = DateTime.Now;
data.CPUPower = 10;

var serializeOptions = new JsonSerializerOptions 
{
    PropertyNamingPolicy = new SnakeCaseNamingPolicy()
};
var json_string = JsonSerializer.Serialize(data, serializeOptions);
Console.WriteLine(json_string);

{"time_stamp":"2020-08-06T00:30:35.3815583-04:00","cpu_power":10}

于 2020-08-06T04:31:45.390 回答
1

有点晚了,但这个解决方案也可以解决像 ABCItem 或 MyCPU 这样的情况。这只是概念,您可以对其进行改进以使其更加通用

using System.Collections.Generic;

namespace Extensions
{
    public static class StringExtension
    {
        public static string ToSnakeCase(this string str)
        {
            // collect the final result
            var snakeCase = new List<char>();

            // check and add chars (using for loop for performance)
            for (int i = 0; i < str.Length; i++)
            {
                if (i > 0 && char.IsUpper(str[i]) && !char.IsUpper(str[i + 1]))
                {
                    snakeCase.Add('_');
                }
                snakeCase.Add(str[i]);
            }

            // build the new string
            return new string(snakeCase.ToArray()).ToLower();
        }
    }
}

var snakeCase = "CPUMeter".ToSnakeCase();
var snakeCase = "PascalCase".ToSnakeCase();
var snakeCase = "camelCase".ToSnakeCase();
于 2021-01-18T19:35:46.293 回答
0

有一个GitHub 存储库并支持SnakeCase,还有一个 nuget 包可用。KebabCaseSystem.Text.Json

nuget

PM> 安装包 JorgeSerrano.Json.JsonSnakeCaseNamingPolicy

SnakeCase 命名策略

var person = new Person { FirstName = "Jorge", Birthday = DateTime.UtcNow, MyJobCity = "Madrid" };

var options = new JsonSerializerOptions { PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy() };
var json = JsonSerializer.Serialize(person, options);

烤肉串案例命名政策

var person = new Person { FirstName = "Jorge", Birthday = DateTime.UtcNow, MyJobCity = "Madrid" };

var options = new JsonSerializerOptions { PropertyNamingPolicy = new JsonKebabCaseNamingPolicy() };
var json = JsonSerializer.Serialize(person, options);
于 2021-06-15T08:33:11.957 回答
0

不需要单独的 kebab-和snake-case 命名策略。此外,如果您绝对想尽量减少不必要的字符转换和查找的数量,可以对转换方法进行一些优化

using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

public class SeperatorNamingPolicy : JsonNamingPolicy
{
    public SeperatorNamingPolicy(char seperator = '_')
    {
        Seperator = seperator;
    }
    public char Seperator { get; }

    public override string ConvertName(string name)
    {
        IEnumerable<char> ToSeperated()
        {
            var e = name.GetEnumerator();
            if (!e.MoveNext()) yield break;
            yield return char.ToLower(e.Current);
            while (e.MoveNext())
            {
                if (char.IsUpper(e.Current))
                {
                    yield return Seperator;
                    yield return char.ToLower(e.Current);
                }
                else
                {
                    yield return e.Current;
                }
            }
        }

        return new string(ToSeperated().ToArray());
    }
}

但是,如果您只想要一个蛇案例命名策略而不向您的代码添加额外的依赖项,那么专用的蛇案例命名策略就足够了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

public class SnakeCaseNamingPolicy : JsonNamingPolicy
{
    public override string ConvertName(string name)
    {
        static IEnumerable<char> ToSnakeCase(CharEnumerator e)
        {
            if (!e.MoveNext()) yield break;
            yield return char.ToLower(e.Current);
            while (e.MoveNext())
            {
                if (char.IsUpper(e.Current))
                {
                    yield return '_';
                    yield return char.ToLower(e.Current);
                }
                else
                {
                    yield return e.Current;
                }
            }
        }

        return new string(ToSnakeCase(name.GetEnumerator()).ToArray());
    }
}

您当然可以通过在您的 startup.cs 中添加 json 选项来使用它:

public void ConfigureServices(IServiceCollection services)
{
    // . . .
    services
        .AddControllers()
        .AddJsonOptions(o => o.JsonSerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy());
    // . . .
}
于 2021-09-23T08:45:09.427 回答