将 JSON 驼峰大小写转换为蛇形大小写(反之亦然)并将数值字符串化

Convert JSON camel case to snake case (and vice versa) and stringify numeric values

我必须向 Web REST 服务发送和接收 JSON 对象。这些对象由 DLL 生成,该 DLL 以大驼峰 ("PropertyName") 序列化属性名称,而 Web 服务需要蛇形 ("property_name")。 此外,DLL 将数值序列化为浮点数,但 REST API 需要所有字符串。 处理对象后,REST 服务 returns snake case JSON.

JSON 很复杂,包含嵌套的数组和对象。从 REST 字符串转换回来时,我可以跳过数字字符串的去字符串化,但我仍然必须将属性名称重新转换为大驼峰式。

我正在考虑使用 Newtonsoft Json 库编写一个助手 class,但它看起来比我预期的要复杂。转换器应接受 JSON 和 return JSON.

示例:

{
    "FirstObject": {
        "NestedObject": {
            "AttributeString": "ok",
            "AttributeNumeric": 123.45
        },
        "OtherObject": [{
            "ArrayVal": 100
        }, {
            "ArrayVal": 200
        }]
    }
}

应该变成

{
    "first_object": {
        "nested_object": {
            "attribute_string": "ok",
            "attribute_numeric": "123.45"
        },
        "other_object": [{
            "array_val": "100"
        }, {
            "array_val": "200"
        }]
    }
}

我看到 Json.Net 库有 SnakeCaseNamingStrategyCamelCaseNamingStrategy class,所以我的想法是使用 JsonTextReader 来解析输入,更改 属性 名称的命名约定,将数值设置为字符串,并使用 JsonTextWriter.

写入修改后的标记

我找不到有关如何执行此操作的任何示例。

最简单的方法是使用一组与您的 JSON 相匹配的模型 class。 (您可以通过将 JSON 的完整示例复制到剪贴板,然后使用 Edit -> Paste Special -> Paste JSON as Classes 函数在 Visual Studio 中生成 classes。)使模型 classes 对 属性 名称使用大驼峰式大小写(这是 C# 的标准命名约定),并使用字符串代替数字属性。

因此,对于您的示例 JSON,模型 classes 将如下所示:

public class RootObject
{
    public FirstObject FirstObject { get; set; }
}

public class FirstObject
{
    public NestedObject NestedObject { get; set; }
    public List<OtherObject> OtherObject { get; set; }
}

public class NestedObject
{
    public string AttributeString { get; set; }
    public string AttributeNumeric { get; set; }
}

public class OtherObject
{
    public string ArrayVal { get; set; }  // using string instead of int here
}

然后,要将驼峰式 JSON 转换为蛇形,您可以这样做:

var obj = JsonConvert.DeserializeObject<RootObject>(json);
var settings = new JsonSerializerSettings
{
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = true }
    },
    Formatting = Formatting.Indented
};
json = JsonConvert.SerializeObject(obj, settings);

原始的 JSON 将自然地反序列化为模型,因为 属性 名称已经匹配。 Json.Net 会根据需要自动将 JSON 中的数值转换为字符串以适应 class 属性。在序列化时,SnakeCaseNamingStrategy 开始发挥作用,将 属性 名称更改为蛇形。数值以字符串形式写出,因为这是在 classes.

中声明属性的方式

要返回另一条路,您可以这样做:

obj = JsonConvert.DeserializeObject<RootObject>(json, settings);  // same settings as above
json = JsonConvert.SerializeObject(obj, Formatting.Indented);

在这里,在反序列化过程中,Json.Net 使用 SnakeCaseNamingStrategy 将模型 属性 名称再次转换为蛇形,以便将它们与 JSON 属性匹配。 JSON 中的数值已经是字符串,因此不需要转换。在序列化时,我们没有使用任何特殊设置,因此 属性 名称完全按照声明写出,这是大驼峰式。保存数值的字符串属性仍然是字符串(你在你的问题中说这是可以的)。

这是一个往返演示:https://dotnetfiddle.net/3Pb1fT


如果您没有可以使用的模型,仍然可以使用您建议的 JsonReader/JsonWriter 方法进行此转换,但需要更多的代码将它们粘合在一起并进行转换。这是一个可以完成大部分繁重工作的辅助方法:

public static void ConvertJson(TextReader textReader, TextWriter textWriter, 
                               NamingStrategy strategy, 
                               Formatting formatting = Formatting.Indented)
{
    using (JsonReader reader = new JsonTextReader(textReader))
    using (JsonWriter writer = new JsonTextWriter(textWriter))
    {
        writer.Formatting = formatting;
        if (reader.TokenType == JsonToken.None)
        {
            reader.Read();
            ConvertJsonValue(reader, writer, strategy);
        }
    }
}

private static void ConvertJsonValue(JsonReader reader, JsonWriter writer, 
                                     NamingStrategy strategy)
{
    if (reader.TokenType == JsonToken.StartObject)
    {
        writer.WriteStartObject();
        while (reader.Read() && reader.TokenType != JsonToken.EndObject)
        {
            string name = strategy.GetPropertyName((string)reader.Value, false);
            writer.WritePropertyName(name);
            reader.Read();
            ConvertJsonValue(reader, writer, strategy);
        }
        writer.WriteEndObject();
    }
    else if (reader.TokenType == JsonToken.StartArray)
    {
        writer.WriteStartArray();
        while (reader.Read() && reader.TokenType != JsonToken.EndArray)
        {
            ConvertJsonValue(reader, writer, strategy);
        }
        writer.WriteEndArray();
    }
    else if (reader.TokenType == JsonToken.Integer)
    {
        // convert integer values to string
        writer.WriteValue(Convert.ToString((long)reader.Value));
    }
    else if (reader.TokenType == JsonToken.Float)
    {
        // convert floating point values to string
        writer.WriteValue(Convert.ToString((double)reader.Value,
                          System.Globalization.CultureInfo.InvariantCulture));        
    }
    else // string, bool, date, etc.
    {
        writer.WriteValue(reader.Value);
    }
}

要使用它,你只需要为你的输入JSON设置一个TextReader,为输出设置一个TextWriter,并传入适当的NamingStrategy你想用于转换。例如,要将原始的 JSON 字符串转换为蛇形大小写,您可以这样做:

using (StringReader sr = new StringReader(upperCamelCaseJson))
using (StringWriter sw = new StringWriter())
{
    ConvertJson(sr, sw, new SnakeCaseNamingStrategy(), formatting);
    string snakeCaseJson = sw.ToString();
    ...
}

或者,如果您的 JSON 的源 and/or 目的地是某种流,您可以使用 StreamReader/StreamWriter 代替:

using (StreamReader sr = new StreamReader(inputStream))
using (StreamWriter sw = new StreamWriter(outputStream))
{
    ConvertJson(sr, sw, new SnakeCaseNamingStrategy(), formatting);
}

现在,return 行程有点问题。 NamingStrategy 只在一个方向有效;它不提供逆转转换的工具。这意味着 Newtonsoft 提供的 NamingStrategy classes 的 none 可以按照您想要的方式将蛇形大小写转换回大驼峰大小写。 CamelCaseNamingStrategy 不会工作,因为它不希望以蛇形大小写开头(它需要大驼峰大小写),而且它的输出无论如何也不是大驼峰。 DefaultNamingStrategy 也不起作用,因为它实际上根本不进行任何转换——它只是一个传递。

解决方案是自定义 NamingStrategy。幸运的是,这并不难做到。只需从基础 NamingStrategy class 派生并实现抽象 ResolvePropertyName 方法:

// This naming strategy converts snake case names to upper camel case (a.k.a. proper case)
public class ProperCaseFromSnakeCaseNamingStrategy : NamingStrategy
{
    protected override string ResolvePropertyName(string name)
    {
        StringBuilder sb = new StringBuilder(name.Length);
        for (int i = 0; i < name.Length; i++)
        {
            char c = name[i];

            if (i == 0 || name[i - 1] == '_')
                c = char.ToUpper(c);

            if (c != '_')
                sb.Append(c);
        }
        return sb.ToString();
    }
}

现在您可以将这个新策略传递给 ConvertJson 方法,如上所述将蛇形大小写 JSON 转换回大驼峰大小写。

往返演示:https://dotnetfiddle.net/jt0XKD